Skip to content
.grsec patch size_overflow

.grsec patch size_overflow

April 29, 2016

grsec patch size_overflow cover: a kernel chip and a -15 that wraps to a huge number

You probably know the Linux kernel hardening patches from grsecurity. Even hardening patches can introduce bugs, and this one is a classic: a signed value quietly turning into an unsigned one.

⚡
TL;DR — A grsec patch changed an int to size_t in the n_tty code. If the computed buffer room goes negative, it wraps to an enormous unsigned number, which can lead to a local DoS via 100% CPU usage.

The bug

I originally found this through a tweet about the size overflow in the grsec patch. That tweet has since been deleted.

The problem line is this one:

room = N_TTY_BUF_SIZE - (ldata->read_head - tail);

When you change a variable from int (signed) to size_t (unsigned), you have to be sure you only ever store non-negative values in it. So what happens if the right-hand side goes negative?

A tiny demo

Here’s the simplest possible example:

$ cat test.c
#include <stdio.h>

int main(void) {
        size_t a = -15;
        printf("%lu\n", a);
        return 0;
}
$ gcc -Wall test.c -o test
$ ./test
18446744073709551601

A small negative value like -15 becomes a gigantic unsigned one. The bits don’t change, only how they are read:

The same 64 bits read as int give -15 and read as size_t give 18446744073709551601

💥
In the tty code, room tells the kernel how much space is left in the buffer. A wrapped value means “almost infinite room”, and that can be abused for a local DoS that pins the CPU at 100%.

The fix

Check that the value isn’t negative before using it. A ternary operator does the job as a quick fix:

room = ((N_TTY_BUF_SIZE - (ldata->read_head - tail)) > 0) ? N_TTY_BUF_SIZE - (ldata->read_head - tail) : 0;
⚠️
This check only works if the subtraction is evaluated as a signed value. If every operand is already unsigned, the result can never be below zero and the > 0 test is always true for any non-zero result. Watch your types.

Lessons learned

  • Changing int to size_t is not a cosmetic change. Every place that can produce a negative value becomes a potential overflow.
  • size_t a = -15; compiles fine, even with -Wall. The compiler won’t save you here.
  • Validate the sign before the assignment, not after it.
Last updated on