public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [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