* [PATCH v2] panic: prevent panic_timeout * 1000 from overflow
[not found] <0d4601d65709a0e2d80e2a8880gmail.com>
@ 2020-07-11 5:17 ` Changming
2020-07-14 1:57 ` Andrew Morton
0 siblings, 1 reply; 5+ messages in thread
From: Changming @ 2020-07-11 5:17 UTC (permalink / raw)
To: willy, rdunlap
Cc: keescook, mcgrof, yzaikin, akpm, linux-kernel, Changming Liu
From: Changming Liu <charley.ashbringer@gmail.com>
Since panic_timeout is an integer passed-in through sysctl,
the loop boundary panic_timeout * 1000 could overflow and
result in a zero-delay panic when panic_timeout is greater
than INT_MAX/1000.
Fix this by moving 1000 to the left, also in case i/1000
might never be greater than panic_timeout, change i to
long long so that it strictly has more bits.
Signed-off-by: Changming Liu <charley.ashbringer@gmail.com>
---
Changes in v2:
- change the loop in panic, instead of change the sysctl
kernel/panic.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/kernel/panic.c b/kernel/panic.c
index e2157ca387c8..941848aac0ee 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -178,7 +178,8 @@ void panic(const char *fmt, ...)
{
static char buf[1024];
va_list args;
- long i, i_next = 0, len;
+ long long i;
+ long i_next = 0, len;
int state = 0;
int old_cpu, this_cpu;
bool _crash_kexec_post_notifiers = crash_kexec_post_notifiers;
@@ -315,7 +316,7 @@ void panic(const char *fmt, ...)
*/
pr_emerg("Rebooting in %d seconds..\n", panic_timeout);
- for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {
+ for (i = 0; i / 1000 < panic_timeout; i += PANIC_TIMER_STEP) {
touch_nmi_watchdog();
if (i >= i_next) {
i += panic_blink(state ^= 1);
--
2.17.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2] panic: prevent panic_timeout * 1000 from overflow
2020-07-11 5:17 ` [PATCH v2] panic: prevent panic_timeout * 1000 from overflow Changming
@ 2020-07-14 1:57 ` Andrew Morton
2020-07-14 2:50 ` Matthew Wilcox
2020-07-16 4:48 ` [PATCH v2] " charley.ashbringer
0 siblings, 2 replies; 5+ messages in thread
From: Andrew Morton @ 2020-07-14 1:57 UTC (permalink / raw)
To: Changming; +Cc: willy, rdunlap, keescook, mcgrof, yzaikin, linux-kernel
On Sat, 11 Jul 2020 01:17:28 -0400 Changming <charley.ashbringer@gmail.com> wrote:
> From: Changming Liu <charley.ashbringer@gmail.com>
>
> Since panic_timeout is an integer passed-in through sysctl,
> the loop boundary panic_timeout * 1000 could overflow and
> result in a zero-delay panic when panic_timeout is greater
> than INT_MAX/1000.
>
> Fix this by moving 1000 to the left, also in case i/1000
> might never be greater than panic_timeout, change i to
> long long so that it strictly has more bits.
>
> ...
>
> --- a/kernel/panic.c
> +++ b/kernel/panic.c
> @@ -178,7 +178,8 @@ void panic(const char *fmt, ...)
> {
> static char buf[1024];
> va_list args;
> - long i, i_next = 0, len;
> + long long i;
> + long i_next = 0, len;
> int state = 0;
> int old_cpu, this_cpu;
> bool _crash_kexec_post_notifiers = crash_kexec_post_notifiers;
> @@ -315,7 +316,7 @@ void panic(const char *fmt, ...)
> */
> pr_emerg("Rebooting in %d seconds..\n", panic_timeout);
>
> - for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {
> + for (i = 0; i / 1000 < panic_timeout; i += PANIC_TIMER_STEP) {
Problem is, 32-bit machines generally cannot perform 64-bit divides.
So a call is emitted to the library function __divsi64() (I forget the exact
name) which Linux doesn't implement (because it's so slow, and we don't
want to be calling it by accident).
So a fix would be to call do_div() or something from
include/linux/div64.h but it's all a great mess.
However we can do native 64-bit multiplication on 32-bit! So how about
something like
--- a/kernel/panic.c~a
+++ a/kernel/panic.c
@@ -313,13 +313,16 @@ void panic(const char *fmt, ...)
* Delay timeout seconds before rebooting the machine.
* We can't use the "normal" timers since we just panicked.
*/
+ u64 timeout = panic_timeout * 1000; /* avoid overflow */
+ u64 timer;
+
pr_emerg("Rebooting in %d seconds..\n", panic_timeout);
- for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {
+ for (timer = 0; timer < timeout; timer += PANIC_TIMER_STEP) {
touch_nmi_watchdog();
- if (i >= i_next) {
- i += panic_blink(state ^= 1);
- i_next = i + 3600 / PANIC_BLINK_SPD;
+ if (timer >= i_next) {
+ timer += panic_blink(state ^= 1);
+ i_next = timer + 3600 / PANIC_BLINK_SPD;
}
mdelay(PANIC_TIMER_STEP);
}
(untested)
There's still the 3600/PANIC_BLINK_SPD in there, but a) that will be
done at compile-time and b) the 64-bit promotion should be done after
the division.
And... oh crap, i_next needs to be 64-bit as well.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2] panic: prevent panic_timeout * 1000 from overflow
2020-07-14 1:57 ` Andrew Morton
@ 2020-07-14 2:50 ` Matthew Wilcox
2020-07-16 23:15 ` [PATCH v3] " Changming Liu
2020-07-16 4:48 ` [PATCH v2] " charley.ashbringer
1 sibling, 1 reply; 5+ messages in thread
From: Matthew Wilcox @ 2020-07-14 2:50 UTC (permalink / raw)
To: Andrew Morton; +Cc: Changming, rdunlap, keescook, mcgrof, yzaikin, linux-kernel
On Mon, Jul 13, 2020 at 06:57:39PM -0700, Andrew Morton wrote:
> +++ a/kernel/panic.c
> @@ -313,13 +313,16 @@ void panic(const char *fmt, ...)
> * Delay timeout seconds before rebooting the machine.
> * We can't use the "normal" timers since we just panicked.
> */
> + u64 timeout = panic_timeout * 1000; /* avoid overflow */
1000ULL to not truncate before the assignment.
> + u64 timer;
... as you implied later
u64 timer, timer_next;
> (untested)
Well, there's the rub. Testing requires 49 days (2^32 / 1000 seconds).
^ permalink raw reply [flat|nested] 5+ messages in thread
* RE: [PATCH v2] panic: prevent panic_timeout * 1000 from overflow
2020-07-14 1:57 ` Andrew Morton
2020-07-14 2:50 ` Matthew Wilcox
@ 2020-07-16 4:48 ` charley.ashbringer
1 sibling, 0 replies; 5+ messages in thread
From: charley.ashbringer @ 2020-07-16 4:48 UTC (permalink / raw)
To: 'Andrew Morton'
Cc: willy, rdunlap, keescook, mcgrof, yzaikin, linux-kernel
> > Since panic_timeout is an integer passed-in through sysctl,
> > the loop boundary panic_timeout * 1000 could overflow and
> > result in a zero-delay panic when panic_timeout is greater
> > than INT_MAX/1000.
> >
> > Fix this by moving 1000 to the left, also in case i/1000
> > might never be greater than panic_timeout, change i to
> > long long so that it strictly has more bits.
> >
> > ...
> >
> > --- a/kernel/panic.c
> > +++ b/kernel/panic.c
> > @@ -178,7 +178,8 @@ void panic(const char *fmt, ...)
> > {
> > static char buf[1024];
> > va_list args;
> > - long i, i_next = 0, len;
> > + long long i;
> > + long i_next = 0, len;
> > int state = 0;
> > int old_cpu, this_cpu;
> > bool _crash_kexec_post_notifiers = crash_kexec_post_notifiers;
> > @@ -315,7 +316,7 @@ void panic(const char *fmt, ...)
> > */
> > pr_emerg("Rebooting in %d seconds..\n", panic_timeout);
> >
> > - for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP)
{
> > + for (i = 0; i / 1000 < panic_timeout; i += PANIC_TIMER_STEP)
{
>
> Problem is, 32-bit machines generally cannot perform 64-bit divides.
> So a call is emitted to the library function __divsi64() (I forget the
exact
> name) which Linux doesn't implement (because it's so slow, and we don't
> want to be calling it by accident).
>
It's good to know, thanks for letting me know why 64-bit division
is slow, and 64-multiplication is fast, surely doing so many
64-bit division will drag a lot, and should be prevented.
> So a fix would be to call do_div() or something from
> include/linux/div64.h but it's all a great mess.
>
> However we can do native 64-bit multiplication on 32-bit! So how about
> something like
>
> --- a/kernel/panic.c~a
> +++ a/kernel/panic.c
> @@ -313,13 +313,16 @@ void panic(const char *fmt, ...)
> * Delay timeout seconds before rebooting the machine.
> * We can't use the "normal" timers since we just panicked.
> */
> + u64 timeout = panic_timeout * 1000; /* avoid overflow */
> + u64 timer;
> pr_emerg("Rebooting in %d seconds..\n", panic_timeout);
> - for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP)
{
> + for (timer = 0; timer < timeout; timer += PANIC_TIMER_STEP)
{
If using u64 as the loop boundary, would it be a problem if
panic_timeout is negative? Since in the current code, if
panic_timeout is negative, the loop will not be executed;
as in the patched code, the loop boundary will be a huge
unsigned value. I guess s64 should do?
If it's not a problem, I'll submit another patch enforcing
the change, including the changes suggested by Matthew here:
> > + u64 timeout = panic_timeout * 1000; /* avoid overflow */
> 1000ULL to not truncate before the assignment.
> > + u64 timer;
> ... as you implied lateru64 timer, timer_next;
Thank you guys so much for your valuable feedback, I learned a lot!
Best,
Changming
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3] panic: prevent panic_timeout * 1000 from overflow
2020-07-14 2:50 ` Matthew Wilcox
@ 2020-07-16 23:15 ` Changming Liu
0 siblings, 0 replies; 5+ messages in thread
From: Changming Liu @ 2020-07-16 23:15 UTC (permalink / raw)
To: akpm; +Cc: willy, rdunlap, keescook, mcgrof, yzaikin, linux-kernel,
Changming Liu
Since panic_timeout is an s32 integer passed in through sysctl, the
loop boundary panic_timeout * 1000 could overflow and result in
a zero-delay panic when panic_timeout is greater than INT_MAX/1000.
Fix this by elevating the precision of the loop boundary via
assigning the result to a u64 integer, also in case the loop
counter i might never be greater than u64 timeout = panic_timeout*1000,
elevate its precision to u64(timer) as well. The same applies to
timer_next replacing i_next which is initialized to 0.
Signed-off-by: Changming Liu <charley.ashbringer@gmail.com>
---
Changes in v3:
- change the loop in panic, instead of change the sysctl
- avoid using 64-bit division, doing 64-bit mult instead
kernel/panic.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/kernel/panic.c b/kernel/panic.c
index e2157ca..ef6cd57 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -313,13 +313,16 @@ void panic(const char *fmt, ...)
* Delay timeout seconds before rebooting the machine.
* We can't use the "normal" timers since we just panicked.
*/
+ u64 timeout = panic_timeout * 1000ULL; /* avoid overflow */
+ u64 timer, timer_next = 0;
+
pr_emerg("Rebooting in %d seconds..\n", panic_timeout);
- for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {
+ for (timer = 0; timer < timeout; timer += PANIC_TIMER_STEP) {
touch_nmi_watchdog();
- if (i >= i_next) {
- i += panic_blink(state ^= 1);
- i_next = i + 3600 / PANIC_BLINK_SPD;
+ if (timer >= timer_next) {
+ timer += panic_blink(state ^= 1);
+ timer_next = timer + 3600 / PANIC_BLINK_SPD;
}
mdelay(PANIC_TIMER_STEP);
}
--
2.7.4
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2020-07-16 23:16 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <0d4601d65709a0e2d80e2a8880gmail.com>
2020-07-11 5:17 ` [PATCH v2] panic: prevent panic_timeout * 1000 from overflow Changming
2020-07-14 1:57 ` Andrew Morton
2020-07-14 2:50 ` Matthew Wilcox
2020-07-16 23:15 ` [PATCH v3] " Changming Liu
2020-07-16 4:48 ` [PATCH v2] " charley.ashbringer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox