computer security 2014 – ymir vigfusson based on “undefined behavior: what happened to my...

Post on 31-Dec-2015

222 Views

Category:

Documents

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

What happened to my code?

Computer Security 2014 – Ymir Vigfusson

Based on “Undefined Behavior: What Happened to My Code?” by Wang et al. APsys 2012 and Wang et al. OSDI ‘2012.

2

Linux AGP: [CVE-2011-1745]

3

Linux AGP: [CVE-2011-1745]

What if pg_start + page_count wraps around?

4

Linux kernel: flow steering

5

Linux kernel: flow steering

sizeof(struct rps_dev_flow) is 8, so the check for count of at most 1<<30 is insufficient to prevent overflow

6

Linux kernel

7

Linux kernel

On 64-bit systems, ULONG_MAX is 64-bits, whereas opt is 32-bits. The check is thus insufficient to avoid overflow.

8

Linux: ceph file system

9

Linux: ceph file system

Do your math correctly. The correct check for “a + b*x” is “x > (uintmax – a) / b”

10

Linux: OLPC display controller

11

Linux: OLPC display controller

Value of status is in the range of 0-255. This check will never be called.

12

Linux: AX.25

13

Linux: AX.25

Remember that a comparing signed and unsigned numbers will be viewed as an unsigned comparison

14

But fixing isn‘t easy ...

15

Linux kernel: lib/mpi/mpi-pow.c int mpi_powm( MPI res, MPI base, MPI exp, MPI mod) { mpi_size_t esize, msize, bsize, rsize; int esign, msign, bsign, rsign; mpi_size_t size; mpi_size_t tsize=0; /* to avoid compiler warning */ ... 

esize = exp->nlimbs; msize = mod->nlimbs; size = 2 * msize; esign = exp->sign; msign = mod->sign;  rp = res->d; ep = exp->d;  if( !msize )

msize = 1 / msize; /* provoke a signal */  if( !esize ) {

/* Exponent is zero, result is 1 mod MOD, i.e., 1 or 0 * depending on if MOD equals 1. */rp[0] = 1;res->nlimbs = (msize == 1 && mod->d[0] == 1) ? 0 : 1;res->sign = 0;goto leave;

}

16

Linux kernel: lib/mpi/mpi-pow.c int mpi_powm( MPI res, MPI base, MPI exp, MPI mod) { mpi_size_t esize, msize, bsize, rsize; int esign, msign, bsign, rsign; mpi_size_t size; mpi_size_t tsize=0; /* to avoid compiler warning */ ... 

esize = exp->nlimbs; msize = mod->nlimbs; size = 2 * msize; esign = exp->sign; msign = mod->sign;  rp = res->d; ep = exp->d;  if( !msize )

msize = 1 / msize; /* provoke a signal */  if( !esize ) {

/* Exponent is zero, result is 1 mod MOD, i.e., 1 or 0 * depending on if MOD equals 1. */rp[0] = 1;res->nlimbs = (msize == 1 && mod->d[0] == 1) ? 0 : 1;res->sign = 0;goto leave;

}

Division by zero is undefined. No exception generated on MIPS/PowerPC. Optimized out by Clang!

17

Linux kernel: lib/ext4/super.cstatic int ext4_fill_flex_info(struct super_block *sb) { struct ext4_sb_info *sbi = EXT4_SB(sb); struct ext4_group_desc *gdp = NULL, ext4_group_t flex_group_count, ext4_group_t flex_group; unsigned int groups_per_flex = 0; size_t size; int i; sbi->s_log_groups_per_flex = sbi->s_es>s_log_groups_per_flex; if (sbi->s_log_groups_per_flex < 1 || sbi->s_log_groups_per_flex > 31) { sbi->s_log_groups_per_flex = 0; return 1; } groups_per_flex = 1 << sbi->s_log_groups_per_flex; if (groups_per_flex == 0) return 1; /* We allocate both existing and potentially added groups */ flex_group_count = ((sbi->s_groups_count + groups_per_flex - 1) + ((le16_to_cpu(sbi->s_es->s_reserved_gdt_blocks) + 1) << EXT4_DESC_PER_BLOCK_BITS(sb))) / groups_per_flex; size = flex_group_count * sizeof(struct flex_groups);

18

Linux kernel: lib/ext4/super.c

Left shift of n-bit integer by n is undefined. Compiler thinks result is positive and removes the check.

static int ext4_fill_flex_info(struct super_block *sb) { struct ext4_sb_info *sbi = EXT4_SB(sb); struct ext4_group_desc *gdp = NULL, ext4_group_t flex_group_count, ext4_group_t flex_group; unsigned int groups_per_flex = 0; size_t size; int i; sbi->s_log_groups_per_flex = sbi->s_es>s_log_groups_per_flex; if (sbi->s_log_groups_per_flex < 1 || sbi->s_log_groups_per_flex > 31) { sbi->s_log_groups_per_flex = 0; return 1; } groups_per_flex = 1 << sbi->s_log_groups_per_flex; if (groups_per_flex == 0) return 1; /* We allocate both existing and potentially added groups */ flex_group_count = ((sbi->s_groups_count + groups_per_flex - 1) + ((le16_to_cpu(sbi->s_es->s_reserved_gdt_blocks) + 1) << EXT4_DESC_PER_BLOCK_BITS(sb))) / groups_per_flex; size = flex_group_count * sizeof(struct flex_groups);

19

Linux kernel: fs/open.c

20

Linux kernel: fs/open.c

Some architectures do not silently wrap around on signed overflow. In C signed integer overflow is undefined (so “x+100 < x” is optimized away!).Here both offset and len are non-negative so GCC decides to remove the check! (Must use –fno-strict-overflow)

21

Linux kernel: lib/vsprintf.c

22

Linux kernel: lib/vsprintf.c

The C standard does not define pointer arithmetic to wrap around, and thus compilers do algebra on the pointers. They cancel buf from “buf + size < buf”, reducing the check to “size < 0”. (Must use –fno-strict-overflow)

23

Linux kernel: fs/open.c

24

Linux kernel: fs/open.c

Dereferencing a NULL pointer is undefined behavior in C, and so compilers assume that all dereferenced pointers are non-null.The check for NULL here is optimized away because of the dereferencing of tun->sk above. (Must use -fno-delete-null-pointer-checks)

25

Linux kernel: include/net/iw_handler.h

26

Linux kernel: include/net/iw_handler.h

27

Linux kernel: include/net/iw_handler.h

“Type-punning” (viewing an object by different types) actually is pretty strict in C. Here, the compiler thinks (char *)iwe and struct iw_event *iwe are different objects and reorders the two instructions above. Thus a stale copy of iwe->len gets copied. (Must use -fno-strict-aliasing)

28

FreeBSD libc: lib/libc/stdlib/rand.c

29

FreeBSD libc: lib/libc/stdlib/rand.c

Misuse of old variable contents on the stack. What if “junk” is assigned to a register? (This is what GCC does). Moreover, the undefined use of junk will make Clang eliminate the entire srandom() computation, making random numbers predictable.

30

PostgreSQL: src/backend/utils/adt/int8.c

31

PostgreSQL: src/backend/utils/adt/int8.c

Compiler is unaware that ereport() will never return. Thus compiler assumes the division will always execute. On some platforms (Alpha, S/390, SPARC, …) GCC will move the division before the check for arg2==0 …

32

Summary

Divison by zero• Not always an exception• Undefined, so be aware of instruction reordering

Oversized shift• Shifting n-bit integer by n bits is undefined in C

Signed integer overflow• Undefined by C. Checks might get optimized out

Out-of-bounds pointers• Pointer arithmetic does not wrap around

Uninitialized reads• Don’t misuse the stack

33

How do we improve the

situation?

34

Possible remedies

• Flagging all unexpected behavior is undecidable

• Lots of options: -ftrapv (signed overflow), -fcatch-undefined-behavior (Clang, but only a subset)

Compiler improveme

nts

• Clang‘s static analyzer, KLEE, Kint, ...• Would not catch offset+len < 0 …

Bug finding tools

• Outlaw undefined behavior in the C standard?

• May heavily restrict compilers from exploiting hardware performance. Trade-off…

Improved standard

35

Sudo in 2012 – Where’s the bug?

void sudo_debug(int level, const char *fmt, ...) { va_list ap; char *fmt2;

if (level > debug_level) return;

/* Backet fmt with prog name and a newline to make it a single write */ easprintf(&fmt2, "%s: %s\n", getprogname(), fmt); va_start(ap, fmt); vfprintf(stderr, fmt2, ap); va_end(ap); efree(fmt2);}

36

Asterisk phones (2012) – Where‘s the bug?

char exten[AST_MAX_EXTENSION]; static int handle_message(struct skinny_req *req, struct skinnysession *s) { case KEYPAD_BUTTON_MESSAGE: struct skinny_device *d = s->device; struct skinny_subchannel *sub; int lineInstance; int callReference; lineInstance = letohl(req->data.keypad.lineInstance); callReference = letohl(req->data.keypad.callReference); if (lineInstance) { sub = find_subchannel_by_instance_reference(d, lineInstance, callReference); } else { sub = d->activeline->activesub; } if (sub && ((sub->owner && sub->owner->_state < AST_STATE_UP) || sub->onhold)) { char dgt; int digit = letohl(req->data.keypad.button); if (digit == 14) { dgt = '*'; } else if (digit == 15) { dgt = '#'; } else if (digit >= 0 && digit <= 9) { dgt = '0' + digit; } else { dgt = '0' + digit; ast_log(LOG_WARNING, "Unsupported digit %d\n", digit); } d->exten[strlen(d->exten)] = dgt; d->exten[strlen(d->exten)+1] = '\0'; } else res = handle_keypad_button_message(req, s); } break;

37

Sendmail – Where‘s the bug?

void sighndlr(int dummy) { syslog(LOG_NOTICE,user_dependent_data); // *** Initial cleanup code, calling the following somewhere: free(global_ptr2); free(global_ptr1); // *** 1 *** >> Additional clean-up code - unlink tmp files, etc << exit(0);}

/************************************************** * This is a signal handler declaration somewhere * * at the beginning of main code. * **************************************************/

signal(SIGHUP,sighndlr); signal(SIGTERM,sighndlr);

// *** Other initialization routines, and global pointer // *** assignment somewhere in the code (we assume that // *** nnn is partially user-dependent, yyy does not have to be):

global_ptr1=malloc(nnn); global_ptr2=malloc(yyy);

// *** 2 *** >> further processing, allocated memory << // *** 2 *** >> is filled with any data, etc... <<

38

Sudo again – Where‘s the bug?/* Log a message to syslog, pre-pending the username and splitting the message into parts if it is longer than MAXSYSLOGLEN. */static void do_syslog( int pri, char * msg ) { int count; char * p; char * tmp; char save;

for ( p=msg, count=0; count < strlen(msg)/MAXSYSLOGLEN + 1; count++ ) { if ( strlen(p) > MAXSYSLOGLEN ) { for ( tmp = p + MAXSYSLOGLEN; tmp > p && *tmp != ' '; tmp-- ) ; if ( tmp <= p ) tmp = p + MAXSYSLOGLEN;

/* NULL terminate line, but save the char to restore later */ save = *tmp; *tmp = '\0';

if ( count == 0 ) SYSLOG( pri, "%8.8s : %s", user_name, p ); else SYSLOG( pri,"%8.8s : (command continued) %s",user_name,p ); /* restore saved character */ *tmp = save; /* Eliminate leading whitespace */ for ( p = tmp; *p != ' '; p++ ) ; } else { if ( count == 0 ) SYSLOG( pri, "%8.8s : %s", user_name, p ); else SYSLOG( pri,"%8.8s : (command continued) %s",user_name,p ); } }}

39

OpenSSH – Where‘s the bug?

/* * Pointer to an array containing all allocated channels. The array is * dynamically extended as needed. */static Channel **channels = NULL;

/* * Size of the channel array. All slots of the array must always be * initialized (at least the type field); unused slots set to NULL */static u_int channels_alloc = 0;

Channel *channel_by_id(int id){

Channel *c;

if (id < 0 || (u_int)id > channels_alloc) {logit("channel_by_id: %d: bad id", id);return NULL;

}c = channels[id];if (c == NULL) {

logit("channel_by_id: %d: bad id: channel free", id);return NULL;

}return c;

}

top related