Summary: | Kernel stack overflow in sysctl handler for kern.binmisc.add | ||||||
---|---|---|---|---|---|---|---|
Product: | Base System | Reporter: | CTurt <ecturt> | ||||
Component: | kern | Assignee: | freebsd-bugs (Nobody) <bugs> | ||||
Status: | Closed FIXED | ||||||
Severity: | Affects Only Me | CC: | emaste, jo, op, op, sbruno, security, shawn.webb, sson | ||||
Priority: | --- | Keywords: | needs-patch, needs-qa, security | ||||
Version: | CURRENT | Flags: | sbruno:
mfc-stable10+
|
||||
Hardware: | Any | ||||||
OS: | Any | ||||||
Attachments: |
|
Description
CTurt
2016-01-30 16:38:26 UTC
PoC which causes panic: https://gist.github.com/CTurt/ddcda1a5ff4a3a38cad2 Please attribute any code fix to "CTurt from HardenedBSD" Does a CVE need to be issued for this vulnerability? No need, I'll write a patch later. I'm busy with other potential bugs at the moment though. I don't think a CVE is needed, since it is only triggerable as root (every time I find a bug, ffs). Is this also missing a sx_xunlock(&interp_list_sx) for the ENOMEM error case? I didn't even notice this before, but you're right. imgact_binmisc_add_entry: sx_xlock(&interp_list_sx); if (imgact_binmisc_find_entry(xbe->xbe_name) != NULL) { sx_xunlock(&interp_list_sx); return (EEXIST); } /* Preallocate a new entry. */ ibe = imgact_binmisc_new_entry(xbe); if (!ibe) return (ENOMEM); SLIST_INSERT_HEAD(&interpreter_list, ibe, link); interp_list_entry_count++; sx_xunlock(&interp_list_sx); If the code ever reaches `return (ENOMEM);`, it is missing an `sx_xunlock(&interp_list_sx);` call. Unfortunately, this bug isn't triggerable, because `imgact_binmisc_add_entry` uses `M_WAITOK` for its allocations, and so can never return `NULL`: static imgact_binmisc_entry_t * imgact_binmisc_new_entry(ximgact_binmisc_entry_t *xbe) { ibe = malloc(sizeof(*ibe), M_BINMISC, M_WAITOK|M_ZERO); ... return (ibe); } My recommendation is to just remove the following check altogether: if (!ibe) return (ENOMEM); Any movement on this? Something like this has been suggested by sson@ to resolve these issues. What do you guys think? diff --git a/sys/kern/imgact_binmisc.c b/sys/kern/imgact_binmisc.c index dd57717..39ca156 100644 --- a/sys/kern/imgact_binmisc.c +++ b/sys/kern/imgact_binmisc.c @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2013-15, Stacey D. Son + * Copyright (c) 2013-16, Stacey D. Son * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -220,16 +220,17 @@ imgact_binmisc_add_entry(ximgact_binmisc_entry_t *xbe) { imgact_binmisc_entry_t *ibe; char *p; + int cnt; if (xbe->xbe_msize > IBE_MAGIC_MAX) return (EINVAL); - for(p = xbe->xbe_name; *p != 0; p++) - if (!isascii((int)*p)) + for(cnt = 0, p = xbe->xbe_name; *p != 0; cnt++, p++) + if (cnt >= IBE_NAME_MAX || !isascii((int)*p)) return (EINVAL); - for(p = xbe->xbe_interpreter; *p != 0; p++) - if (!isascii((int)*p)) + for(cnt = 0, p = xbe->xbe_interpreter; *p != 0; cnt++, p++) + if (cnt >= IBE_INTERP_LEN_MAX || !isascii((int)*p)) return (EINVAL); /* Make sure we don't have any invalid #'s. */ @@ -266,8 +267,6 @@ imgact_binmisc_add_entry(ximgact_binmisc_entry_t *xbe) /* Preallocate a new entry. */ ibe = imgact_binmisc_new_entry(xbe); - if (!ibe) - return (ENOMEM); SLIST_INSERT_HEAD(&interpreter_list, ibe, link); interp_list_entry_count++; Created attachment 168847 [details]
Proposed patch
Attached is a proposed patch. Thanks to emaste@ for bring this to my attention and to CTurt and Shawn for their work on this. If approved then please feel free to commit.
With the patch, the propsed test code does not crash the machine and returns: root@tasty.ysv:/var/tmp # ./bad_test result -1 errno 22 Without this patch from sson, machine panics as described: panic: stack overflow detected; backtrace may be corrupted cpuid = 3 KDB: stack backtrace: db_trace_self_wrapper() at db_trace_self_wrapper+0x2b/frame 0xfffffe046ad0cb40 vpanic() at vpanic+0x182/frame 0xfffffe046ad0cbc0 panic() at panic+0x43/frame 0xfffffe046ad0cc20 __stack_chk_fail() at __stack_chk_fail+0x12/frame 0xfffffe046ad0cc30 sysctl_kern_binmisc() at sysctl_kern_binmisc+0x7b4/frame 0xfffffe046ad0d8b0 KDB: enter: panic [ thread pid 16434 tid 101185 ] Stopped at kdb_enter+0x3b: movq $0,kdb_why db> bt Tracing pid 16434 tid 101185 td 0xfffff8010054b9a0 kdb_enter() at kdb_enter+0x3b/frame 0xfffffe046ad0cb40 vpanic() at vpanic+0x19f/frame 0xfffffe046ad0cbc0 panic() at panic+0x43/frame 0xfffffe046ad0cc20 __stack_chk_fail() at __stack_chk_fail+0x12/frame 0xfffffe046ad0cc30 sysctl_kern_binmisc() at sysctl_kern_binmisc+0x7b4/frame 0xfffffe046ad0d8b0 db> @CTurt, could you please very / review the patch? (In reply to Stacey Son from comment #9) This patch looks good to me; correctly checks the size of the `xbe_name` and `xbe_interpreter` strings early on, before any out of bounds reads or writes could be performed. I've taken another look at the code and found another potential bug. I'm not certain if this is a bug yet, but I'd also like to bring the following code from `imgact_binmisc_add_entry` to attention: /* Make sure we don't have any invalid #'s. */ p = xbe->xbe_interpreter; while (1) { p = strchr(p, '#'); if (!p) break; p++; switch(*p) { case ISM_POUND: /* "##" */ p++; break; case ISM_OLD_ARGV0: /* "#a" */ p++; break; case 0: default: /* Anything besides the above is invalid. */ return (EINVAL); } } From the comment, and usage of a loop, it seems like this code should be checking that every '#' character in the string follows either another '#' or an 'a' character, however there is no way that this loop will ever be executed more than once since all conditions lead to `break` or `return`. In its current form the code will only validate the first '#' character. To instead check that _every_ '#' character follows a valid character (and not just the first '#' character), the `case`s should `continue` the loop as below: /* Make sure we don't have any invalid #'s. */ p = xbe->xbe_interpreter; while (1) { p = strchr(p, '#'); if (!p) break; p++; switch(*p) { case ISM_POUND: /* "##" */ p++; continue; case ISM_OLD_ARGV0: /* "#a" */ p++; continue; case 0: default: /* Anything besides the above is invalid. */ return (EINVAL); } } (In reply to CTurt from comment #14) Don't worry about this actually, the `break` exits the `switch`, and the loop will continue, checking all '#' characters, no need for change here. A commit references this bug: Author: sbruno Date: Fri Apr 1 16:16:26 UTC 2016 New revision: 297488 URL: https://svnweb.freebsd.org/changeset/base/297488 Log: Repair a overflow condition where a user could submit a string that was not getting a proper bounds check. Thanks to CTurt for pointing at this with a big red blinking neon sign. PR: 206761 Submitted by: sson Reviewed by: cturt@hardenedbsd.org MFC after: 3 days Changes: head/sys/kern/imgact_binmisc.c A commit references this bug: Author: sbruno Date: Tue Apr 5 18:27:47 UTC 2016 New revision: 297588 URL: https://svnweb.freebsd.org/changeset/base/297588 Log: MFC r297488 Repair an overflow condition where a user could submit a string that was not getting a proper bounds check. PR: 206761 Submitted by: sson Reviewed by: cturt@hardenedbsd.org Changes: _U stable/10/ stable/10/sys/kern/imgact_binmisc.c |