...
Code Block | ||||
---|---|---|---|---|
| ||||
jmp_buf buf; unsigned char b[] = {0xe5, 0x06, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00}; int main(void) { setup(); do_stuff(); return 0; } void setup(void) { f(); } void f(void) { g(); } void g(void#include <setjmp.h> #include <stdio.h> #include <stdlib.h> static jmp_buf buf; static void bad(void); static void g(void) { if (setjmp(buf) == 0) { printf("setjmp() invoked\n"); } else { printf("longjmp() invoked\n"); } } static void do_stufff(void) { g(); } static char a[8]; memcpy(a, b, 8)void setup(void) { f(); } void do_stuff(void) { void (*b)(void) = bad; /* ... */ longjmp(buf, 1); } static void bad(void) { printf("Should not be called!\n"); exit(1); } int main(void) { setup(); do_stuff(); } |
Implementation Details
Compiled for x86at -64 O0 using GCC 4.1.2 on Linux, the preceding 7.5 or Clang 8.0 on Ubuntu 18.04 (Linux for x86-64), the preceding example outputs the following when run:
...
Because g()
has finished executing at the time longjmp()
is called, it is no longer on the stack. When do_stuff()
is invoked, its stack frame occupies the same memory as the old stack frame of g()
. In this case, a
was located in the same location as the return address of function g()
. The call to memcpy()
assignment of b
overwrites the return address, so when longjmp()
sends control back to function g()
, the function returns to the wrong address (in this case, to function bad()
).
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <setjmp.h> #include <stdio.h> #include <stdlib.h> static jmp_buf buf; unsignedstatic char b[] = {0xe5, 0x06, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00}void bad(void); intvoid maindo_stuff(void) { ifvoid (setjmp*b)(bufvoid) == 0) {bad; /* printf("setjmp() invoked\n"... */ longjmp(buf, 1); } static void } else bad(void) { printf("longjmp() invokedShould not be called!\n"); } do_stuff(exit(1); return 0; } voidint do_stuffmain(void) { char a[8]; memcpy(a, b, 8); /* ... */ longjmp(buf, 1); } void bad(void) { if (setjmp(buf) == 0) { printf("setjmp() invoked\n"); } else { printf("Should not be called!longjmp() invoked\n"); } exitdo_stuff(1); } |
There is no risk of overwriting a return address because the stack frame of main()
(the function that invoked setjmp()
) is still on the stack; so when do_stuff()
is invoked, the two stack frames will not overlap.
...
Code Block | ||||
---|---|---|---|---|
| ||||
jmp_buf buf; void f(void) { int i = 0; if (setjmp(buf) != 0) { printf("%i\n", i); /* ... */ } i = 2; g(); } void g(void) { /* ... */ longjmp(buf, 1); } |
Implementation Details
Calling f()
will print 2
if you compile with -O0
, but will print 0
if you compile with -O2
. This involves using GCC 7.5 or Clang 8.0 on Ubuntu 18.04 (Linux x86-64).
Compliant Solution
If an object local to the function that invoked setjmp()
needs to be accessed after longjmp()
returns control to the function, the object should be volatile-qualified:
Code Block | ||||
---|---|---|---|---|
| ||||
jmp_buf buf; void f(void) { volatile int i = 0; if (setjmp(buf) != 0) { printf("%i\n", i); /* ... */ } i = 2; g(); } void g(void) { /* ... */ longjmp(buf, 1); } |
This will now correctly print 2
regardless of optimization level.
Risk Assessment
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
MSC22-C | Low | Probable | Medium | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
CodeSonar |
| BADFUNC.LONGJMP BADFUNC.SETJMP | Use of longjmp Use of setjmp | ||||||
LDRA tool suite |
| 43 S | Enhanced enforcement | ||||||
Parasoft C/C++test |
|
|
|
CERT_C-MSC22-a | The facilities provided by <setjmp.h> should not be used | ||||||
Polyspace Bug Finder |
|
| CERT C: Rec. MSC22-C | Checks for use |
of setjmp/longjmp |
setjmp
and longjmp
cause deviation from normal control flow
(rec. fully covered) | ||||||||
SonarQube C/C++ Plugin |
| S982 |
...
...