Linux usermode

Linux usermode #

This list covers common checks for and footguns of C/C++ standard libraries when used in Unix environments.

  • Run checksec to 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 non-thread-safe functions in multi-threaded programs.
    • Some of these functions—such as gethostbyname, inet_ntoa, strtok, and localtime—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.
  • Check for uses of non-reentrant functions in signal handlers. See lcamtuf’s article.
    • The errno should not be modified in signal handlers (or must be saved and restored).
  • 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.
    • memcmp may read out of bounds if the size argument is not computed correctly.
    • strncmp with strings of different length and invalid size may read out of bounds. See cstrnfinder for string comparison bugs found in the wild.
  • Check that environment variables are treated with care.
    • getenv and setenv are 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 procfs filesystem.
      • 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_END prctl operations 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_getenv instead of getenv when possible
  • Check that open and other related filesystem functions are treated with care.
    • Calls to access (to check for file existence) followed by calls to open are vulnerable to race conditions.
    • Calls to rename with attacker control over any part of the destination argument are vulnerable to race conditions.
    • Calls to open with the O_NOFOLLOW flag resolve directory symlinks; usually RESOLVE_NO_SYMLINKS or O_NOFOLLOW_ANY should be used instead.
    • Calls to open without the O_CLOEXEC flag leak file descriptors to child processes.
  • Check that privilege dropping (through use of seteuid, setgid, etc., as well as implicit privilege dropping like during execve calls) is implemented with care.
  • Look for uses of the many unsafe stdlib functions that should not be used.
    • Such functions include sprintf, vsprintf, strcpy, stpcpy, strcat, gets, scanf with %s (no bounds checking), tmpnam, tempnam, mktemp (race conditions), alloca, and putenv (overcomplicated memory management).
  • 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 mmap function returns MAP_FAILED on error, not NULL.
    • Functions like atoi do not inform about errors at all and likely should not be used.
  • Look for proper handling of functions whose return values are insufficient to distinguish success from failure. For such functions, the errno must be cleared before a call (otherwise, the errno value may be a leftover from some previous function call).
    • Functions like clock and times return -1 on error and for legitimate wrap-around of the clock tick.
    • Functions like strtol, strtoull, pow, and log return boundary values (like LONG_MAX) on both valid input and ERANGE overflow.
    • Functions like getchar and fgetc return EOF for errors and actual end-of-file states.
    • Functions like dlsym return NULL on error and for some legitimate non-error cases. The dlerror method must be consulted in addition to the return value.
  • Examine uses of snprintf to 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 read and write may not error out but still not finish the job. For example, read may have not read the requested amount of bytes and likely should be repeated.
  • Look for proper error handling on calls to write, read, and other functions that do not handle EINTR errors.
  • 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 source plus offset memory overlaps with destination, the function call may result in undefined behavior.
  • Examine uses of strlen combined with strcpy, as this combination is likely to miscount the null byte.
    • strlen does not include the NULL terminator in the returned length, but strcpy copies the NULL. 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 prevents scanf from changing the x variable, potentially leading to such leaks.
  • Examine uses of strncat, as it is commonly misused.
    • The size argument is for the size of the source string, not the destination buffer.
  • Examine uses of strncpy, as it may not always null-terminate the destination string.
  • Look for uses of glibc’s qsort, std::sort and std::stable_sort functions called with a non-transitive sort function, as they are exploitable.
  • Look for uses of memcpy and memmove (and possibly other functions) with negative size arguments, as these cases are likely to be exploitable.
    • Negative integer overflows to the large size_t look 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.
  • Look for calls to spinlock functions (like pthread_spin_trylock) on a non-initialized lock.
  • Look for uses of the inet_aton function 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").
  • Check for vulnerable uses of connect(AF_UNSPEC), as it can be used to disconnect an already connected TCP socket.
  • Check for cases in which sockets may be only half-closed (via the shutdown function).
    • 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 the format attribute is used to prevent variadic type misuse.
  • Examine uses of va_start to ensure it is always used with va_end. See this StackOverflow discussion for details.
  • Check that zero is not used in place of NULL.
This content is licensed under a Creative Commons Attribution 4.0 International license.