Linux usermode #
This list covers common checks for and footguns of C/C++ standard libraries when used in Unix environments.
- Run
checksecto learn about the executable’s exploit mitigations.- Check for uses of NX, PIE, stack cookies, RELRO,
FORTIFY_SOURCE, stack clash protector, SafeStack, ShadowCallStack, and other mitigations. - Check that production releases do not contain debug information.
- Check for uses of NX, PIE, stack cookies, RELRO,
- Check for uses of
non-thread-safe functions in multi-threaded programs.
- Some of these functions—such as
gethostbyname,inet_ntoa,strtok, andlocaltime—may return pointers to static data. These pointers must be treated with care even in single-threaded programs, as they all may point to the same data.
- Some of these functions—such as
- Check for uses of non-reentrant functions in signal handlers. See
lcamtuf’s article.
- The
errnoshould not be modified in signal handlers (or must be saved and restored).
- The
- Check that comparisons do not read data out of bounds.
std::equal, when called with three iterators to collections of unequal lengths, reads out of bounds.memcmpmay read out of bounds if the size argument is not computed correctly.strncmpwith strings of different length and invalid size may read out of bounds. Seecstrnfinderfor string comparison bugs found in the wild.
- Check that environment variables are treated with care.
getenvandsetenvare not thread-safe (though this was recently improved in glibc).- Letting users control environment variables is usually unsafe (consider
bash exported functions and
LIBC_FATAL_STDERR_, for example). - If a high-privilege process creates a lower-privilege one, the new process can read its parent environment variables via the
procfsfilesystem.setenv(SOME_SENSITIVE_ENV, "overwrite", 1)leaves the old environment value on the stack (readable via/proc/$pid/environ). Note that this may also be a DoS vector.PR_SET_MM_ENV_START/PR_SET_MM_ENV_ENDprctloperations can be used to hide the environment. Overwriting of the parent process’s stack memory at relevant addresses can also be used to hide the environment.
- General-purpose libraries should use
secure_getenvinstead ofgetenvwhen possible
- Check that
openand other related filesystem functions are treated with care.- Calls to
access(to check for file existence) followed by calls toopenare vulnerable to race conditions. - Calls to
renamewith attacker control over any part of thedestinationargument are vulnerable to race conditions. - Calls to
openwith theO_NOFOLLOWflag resolve directory symlinks; usuallyRESOLVE_NO_SYMLINKSorO_NOFOLLOW_ANYshould be used instead. - Calls to
openwithout theO_CLOEXECflag leak file descriptors to child processes.
- Calls to
- Check that privilege dropping (through use of
seteuid,setgid, etc., as well as implicit privilege dropping like duringexecvecalls) is implemented with care.- Return values of privilege dropping functions must be checked.
- Some function call combinations may fail to drop privileges without returning any errors. For example,
seteuid(X)followed bysetuid(X)may succeed without error but fail to drop privileges permanently. - Group privileges should be dropped before user privileges.
- Ideally, the new privileges
are explicitly checked after the dropping. For example,
setuid(X)should be followed byif (getuid() == X). - Supplementary groups must be cleared with
setgroupscall when needed.
- Some function call combinations may fail to drop privileges without returning any errors. For example,
- Running multiple threads with the same address-space but with different privilege levels is risky. See
vfork’s caveats. - Permissions set through
ioperm, record locks, interval timers, and resource usage information are preserved across calls toexecve(but notfork). - File descriptors (regular,
locks,
timers, etc.),
affinity masks,
scheduling policies,
signal masks,
session IDs,
process group IDs,
supplementary groups,
resource limits, and
NO_NEW_PRIVSprctl settings are preserved across calls toforkandexecve. - Inheritance of capabilities is complex. Read the manual for every capability that your parent program has and that a child should not inherit.
- Return values of privilege dropping functions must be checked.
- Look for uses of the many unsafe stdlib functions that should not be used.
- Such functions include
sprintf,vsprintf,strcpy,stpcpy,strcat,gets,scanfwith%s(no bounds checking),tmpnam,tempnam,mktemp(race conditions),alloca, andputenv(overcomplicated memory management).
- Such functions include
- Check that all errors returned as return values are handled correctly.
- Look for return value checks on calls to
(v)s(n)printf,write,read, and other functions that may return negative values. Mishandling negative returns is quite a classic issue. - The
mmapfunction returnsMAP_FAILEDon error, notNULL. - Functions like
atoido not inform about errors at all and likely should not be used.
- Look for return value checks on calls to
- Look for proper handling of functions whose return values are insufficient to distinguish success from failure. For such functions, the
errnomust be cleared before a call (otherwise, theerrnovalue may be a leftover from some previous function call).- Functions like
clockandtimesreturn-1on error and for legitimate wrap-around of the clock tick. - Functions like
strtol,strtoull,pow, andlogreturn boundary values (likeLONG_MAX) on both valid input andERANGEoverflow. - Functions like
getcharandfgetcreturnEOFfor errors and actual end-of-file states. - Functions like
dlsymreturnNULLon error and for some legitimate non-error cases. Thedlerrormethod must be consulted in addition to the return value.
- Functions like
- Examine uses of
snprintfto ensure the return value is handled correctly.- Its return value is confusing and often misunderstood: it returns the number of characters that would have been written to the final string if enough space had been available, not how many bytes were actually written.
- Look for proper handling of functions that return success without completing the job.
- Functions like
readandwritemay not error out but still not finish the job. For example,readmay have not read the requested amount of bytes and likely should be repeated.
- Functions like
- Look for proper error handling on calls to
write,read, and other functions that do not handleEINTRerrors.- Calls to most stdlib functions should usually be repeated after this error. See this resource on EINTR errors.
- The
closefunction is an exception; it must not be called again after theEINTRerror.
- Look for overlapping buffers as inputs to
snprintf,vsprintf,memcpy, and other functions, as they may be problematic.- Passing the same buffer as input and output often results in undefined behavior.
- When
sourceplusoffsetmemory overlaps withdestination, the function call may result in undefined behavior.
- Examine uses of
strlencombined withstrcpy, as this combination is likely to miscount the null byte.strlendoes not include theNULLterminator in the returned length, butstrcpycopies theNULL. See this X post for details.
- Examine inputs to
scanf("%d", &x)for cases that could lead to uninitialized data leaks.- Passing invalid characters like
-and+as input preventsscanffrom changing thexvariable, potentially leading to such leaks.
- Passing invalid characters like
- Examine uses of
strncat, as it is commonly misused.- The
sizeargument is for the size of thesourcestring, not thedestinationbuffer.
- The
- Examine uses of
strncpy, as it may not always null-terminate the destination string. - Look for uses of glibc’s
qsort,std::sortandstd::stable_sortfunctions called with a non-transitive sort function, as they are exploitable. - Look for uses of
memcpyandmemmove(and possibly other functions) with negativesizearguments, as these cases are likely to be exploitable.- Negative integer overflows to the large
size_tlook to be non-exploitable, as large writes should trigger a crash before anything useful can be done. However, optimizations may make the overflow exploitable, depending on the libc version and CPU features.
- Negative integer overflows to the large
- Look for calls to spinlock functions (like
pthread_spin_trylock) on a non-initialized lock. - Look for uses of the
inet_atonfunction to check if a string is a valid IP address. It should not be used in this way, as the function is not strict.- When linked with glibc, it returns success if the passed-in host address starts with a valid IP address, not just if it is a valid IP address. For example, this call returns success:
inet_aton("1.1.1.1 whatever").
- When linked with glibc, it returns success if the passed-in host address starts with a valid IP address, not just if it is a valid IP address. For example, this call returns success:
- Check for vulnerable uses of
connect(AF_UNSPEC), as it can be used to disconnect an already connected TCP socket.- The socket can be reconnected to a new address. This trick allowed for nsjail escapes in the past.
- Check for cases in which sockets may be only half-closed (via the
shutdownfunction).- This could be useful for exploitation when the remote endpoint has a vulnerability only after the connection was closed but data still needs to be read (or written) via the socket.
- Look for dynamic-size structs implemented with zero-length and one-element arrays, as they are error-prone.
- Examine any custom
printf-like functions to ensure that theformatattribute is used to prevent variadic type misuse. - Examine uses of
va_startto ensure it is always used withva_end. See this StackOverflow discussion for details. - Check that
zero is not used in place of
NULL.