Fortified System Functions

QNX SDP8.0QNX OS System Security GuideAPIConfiguration

QNX OS fortified system functions are designed to detect out-of-bounds memory accesses by performing lightweight parameter validation at compile time, runtime, or both.

An out-of-bounds memory write vulnerability may allow an attacker to modify program behavior or execute arbitrary instructions.

The fortified system functions that QNX OS provides are designed to prevent the following scenarios:

  • A function writes data outside the bounds of a destination object whose length can be determined at compile time.
  • A function that accepts a variable number of arguments processes more arguments than the number actually specified by the caller.
  • A formatted I/O function writes data to one or more unintended destinations because there is an n conversion specifier (e.g., %n) in the format string.

Because these anomalies can corrupt the execution state of the program, it may not be possible to successfully apply a mitigation if they are detected after the fact. For this reason, the fortified system functions terminate the process rather than allow the overflow or other anomaly to occur.

When parameter validation fails at compile-time, the compiler emits a warning or error message.

When parameter validation fails at runtime, diagnostic information is output to the controlling terminal and the process is terminated.

A fortified system function is only able to catch a potential out-of-bounds memory write when the compiler is able to determine the length of the destination object passed to that function. When the compiler is unable to determine the length of the destination object, the fortified system function behaves exactly as the regular version of that system function would.

However, unlike development tools that detect out-of-range memory accesses more exhaustively (e.g., AddressSanitizer (ASan), Valgrind Memcheck), fortified system functions add negligible performance overhead. For this reason, QNX recommends using this feature on both development and production systems.

Building components to use fortified system functions

You must rebuild your executables and libraries to take advantage of QNX OS fortified system functions.

To enable or disable fortified system functions, define the _FORTIFY_SOURCE feature test macro with one of the following values:

0 or undefined
Disables fortified system functions.
1
Enables fortified system functions. Programs that call system functions as described in the QNX OS documentation run normally.
2
Enables fortified system functions with more stringent parameter validation. Even if a program's use of system functions follows the QNX OS documentation, it might fail (e.g., because it supports an unsafe feature).

Using this value makes parameter validation more stringent for some functions, mostly the string-related ones. To determine if an overflow will occur, the bounds of individual structure members are evaluated, not just the bounds of the objects they are a component of.

For example, the compiler does not emit an error when the following code is compiled with a _FORTIFY_SOURCE setting of 1, but it does emit one when _FORTIFY_SOURCE is 2:

struct {
    char key[8];
    int  value;
} key_value_pair;

strcpy(key_value_pair.key, "too long");

This setting also disables the n conversion specifier for formatted I/O functions (e.g., %n).

By default, binaries under target/qnx7/ are built with _FORTIFY_SOURCE enabled at level 2 (with a few exceptions due to incompatibilities).

You can use the qchecksec utility to determine both whether -D_FORTIFY_SOURCE=[1|2] was specified when the source was compiled. See the qchecksec entry in the Utilities Reference.

Required compiler options

When you rebuild your executables and libraries to take advantage of QNX OS fortified system functions, make sure that you use compatible compiler optimization settings. The default compiler optimization settings used by QNX OS are compatible (see information on the OPTIMIZE_TYPE macro in the The qrules.mk include file section in the Programmer's Guide).

If you explicitly specify settings, fortified system functions require either:
  • an optimization level of 1 or higher (-O1, -O2, etc.; because a setting of 1 might not detect as many buffer overflow anomalies, QNX recommends 2 or higher), or
  • optimization for code size (-Os).

The compiler emits a warning at compile time if the optimization settings are incompatible with _FORTIFY_SOURCE.

The use of fortified system functions is implicitly disabled when compiler optimization is disabled.
Note:
Fortified system functions make explicit use of the compiler's built-in implementations of certain libc functions. The -fno-builtin and -fno-builtin-function compiler options have no effect on this behavior. For this reason, the use of fortified system functions may not be appropriate for source files or projects that require the use of -fno-builtin or -fno-builtin-function.

Enabling the use of fortified system functions for an entire project

Enabling fortified system functions in makefiles via the CPPFLAGS variable is a convenient way for a developer to selectively enable or disable the feature for all modules. For example:

CPPFLAGS += -D_FORTIFY_SOURCE=2

Enabling the use of fortified system functions for specific source files

Enabling fortified system functions in source code allows a developer to selectively enable or disable the feature on a module-by-module basis. For example:

#if defined(_FORTIFY_SOURCE) && ( _FORTIFY_SOURCE < 2 )
#undef _FORTIFY_SOURCE
#endif
#ifndef _FORTIFY_SOURCE
#define _FORTIFY_SOURCE  2
#endif
 
#include <stdio.h>
#include <string.h>
 
int main(int argc, char **argv)
{
    /* ... */
}

Enabling the use of fortified system functions via the command line

A developer, tester, or system integrator can use the CPPOPTS environment variable to rebuild an existing application or library with a different _FORTIFY_SOURCE setting without having to modify any source files or makefiles. For example:

make clean && CPPOPTS="-D_FORTIFY_SOURCE=2" make

In some cases, -U_FORTIFY_SOURCE is required before -D_FORTIFY_SOURCE=2 to override a _FORTIFY_SOURCE setting that is specified in one or more makefiles.

Best practices

The following best practices can help you optimize the benefits of using fortified system functions.

Use objects of constant size

Fortified system functions can only perform checks for out-of-bounds memory writes when the size of the destination object is known to be constant at compile time. When practical:
  • Specify integer constant expressions rather than variables as size arguments to memory allocation functions like malloc(), calloc(), and alloc().
  • Avoid variable-length arrays (VLAs).

Avoid using msg + 1 to refer to the payload that follows a message header

Consider the following type for a message header:
struct msg_s {
    uint16_t type;   /* Message type */
    uint16_t length; /* Number of bytes of data that immediately follow */
};
If the pointer to an instance of that message header type is msg, it is convenient to use the idiom msg + 1 to refer to the memory that immediately follows the header (the payload). Unfortunately, because fortified system functions frequently can't distinguish this well-intentioned pointer expression from an anomalous out-of-bounds array access, this expression can lead to false-positive failures. Instead, you should use the _PAYLOAD_OF() macro to get a pointer to the memory that follows a specific header member. Don't use the _DEVCTL_DATA() macro; this is deprecated.

To avoid this problem, whenever possible, define message types that represent a variable-length payload using a flexible array member and use that member to refer to the payload. For example, to modify the example message header type to use a flexible array member:

struct msg_s {
    uint16_t type;   /* Message type */
    uint16_t length; /* Number of bytes of data that immediately follow */
    char data[];     /* Payload */
};

Use the new member to refer to the payload (e.g., msg->data) instead of the expression msg + 1.

Downgrade individual source files to _FORTIFY_SOURCE level 1 to work around false positives

If compiling your project with _FORTIFY_SOURCE level 2 results in compile-time or runtime failures that you've determined are false positives, consider downgrading only the affected source files to _FORTIFY_SOURCE level 1, rather than the entire project. To downgrade individual source files, include the following preprocessor logic before all #include directives in each file:
#if defined(_FORTIFY_SOURCE) && ( _FORTIFY_SOURCE > 1 )
#undef _FORTIFY_SOURCE
#define _FORTIFY_SOURCE 1
#endif

Diagnostic messages

Warning or error messages that the compiler emits as part of the fortified system functions feature have the prefix _FORTIFY_SOURCE:.

In some cases, some additional interpretation is required to find the source of an error generated by fortified system functions.

When fortified system functions are enabled and client source code makes a call to a fortified system function, the system instead calls a function that has the same signature as the original, fortified function (i.e., same return value, same number of parameters, and same parameter types). This inline wrapper function performs additional validation of the function's parameters and its location is used by any errors it generates. However, the actual source of the error is in the client code that called the original system function.

For example, the source file /home/jdoe/foo/bar.c contains the following code:

#include <limits.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    char buf[16] = "";

    if ( argc > 1 ) {
        strlcpy(buf, argv[1], PATH_MAX);
    }

    printf("buf == '%s'\n", buf);

    return 0;
}

This code generates the following error, which indicates that the source of the error is the call to strlcpy():

In file included from /home/jdoe/qnx/sdp/target/qnx7/usr/include/string.h:176,
                 from /home/jdoe/foo/bar.c:3:
In function 'strlcpy',
    inlined from 'main' at /home/jdoe/foo/bar.c:10:9:
/home/jdoe/qnx/sdp/target/qnx7/usr/include/string_chk.h:308:13: error: call to
 '__fortify_fail_overflow_dst_diag_strlcpy' declared with attribute error:
 _FORTIFY_SOURCE: argument 3 of 'strlcpy' is greater than the length of the
 object referenced by argument 1
             __fortify_fail_overflow_dst_diag_strlcpy();
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When the fortified system functions feature terminates a process at runtime, it outputs a diagnostic message to the process's controlling terminal. This diagnostic message includes a brief description of the anomaly (e.g., buffer overflow) and states that the process has been terminated.

Description in message Description of anomaly
destination buffer overflow Data to write would overflow the destination object.

OR

An index or size argument specifies a location that's outside the boundaries of a destination object.
function called with insufficient arguments A function was called with fewer arguments than is expected given the value or values of one or more non-optional arguments.
function called with too many arguments A function was called with a greater number of arguments than is ever considered valid for that function.
format string contains 'n' conversion specifier A function that performs formatted output (e.g., printf()) was called with a format string containing the n conversion specifier (e.g., %n).

Limiting the severity of compile-time diagnostic messages to warnings

To reduce the effort required to enable the _FORTIFY_SOURCE feature on an existing project, define the _FORTIFY_SOURCE_WARNINGS_ONLY feature test macro with a value of 1. When operating in this mode and parameter validation fails for a call to a fortified system function, the compiler emits a warning instead of an error. When the resulting binary is executed, the process terminates at that same fortified system function call. This behavior allows you to get a complete list of the issues to fix without having to rebuild your project multiple times.

You can use the same methods you use to define _FORTIFY_SOURCE to define _FORTIFY_SOURCE_WARNINGS_ONLY (i.e., source code, makefiles, command line).

When you enable the _FORTIFY_SOURCE_WARNINGS_ONLY feature, make sure you specify the -Wsystem-headers compiler option. Otherwise, the compiler may mask some of the warnings that are emitted as errors when _FORTIFY_SOURCE_WARNINGS_ONLY is not enabled.

Customizing runtime behavior

Runtime diagnostic messages outputted by fortified functions in response to a failed parameter validation can be output to standard error by setting the LIBC_FATAL_STDERR to a non-empty value. By default, the messages will be written to /dev/tty. Refer to Commonly Used Environmental Variables for more information.

To customize the behavior of fortified functions after outputting a diagnostic message, use signal() to register a SIGABRT signal handler.

Note:
This will affect all instances where a SIGABRT signal is raised, including those unrelated to fortified functions

Debugging with fortified system functions

If you look at disassembled binaries for systems where fortified system functions are enabled (e.g., as part of your debugging process), you may see instances where a variant of a system function is called where your source code calls a system function. These variants perform parameter validation at runtime and terminate the process when validation fails. When you enable the use of fortified system functions, the compiler replaces some or all of the calls to the following system functions with calls to the corresponding variant:

Function Variant
asprintf __asprintf_chk
c16rtomb __c16rtomb_chk
c32rtomb __c32rtomb_chk
dprintf __dprintf_chk
fprintf __fprintf_chk
fwprintf __fwprintf_chk
gets __gets_chk
getwd __getwd_chk
printf __printf_chk
readblock __readblock_chk
readdir64_r __readdir64_r_chk
readdir_r __readdir_r_chk
realpath __realpath_chk
searchenv __searchenv_chk
snprintf __snprintf_chk
sprintf __sprintf_chk
stpcpy __stpcpy_chk, __strcpy_chk, __memcpy_chk
strcat __strcat_chk, __strcpy_chk, __memcpy_chk
strncat __strncat_chk, __strcat_chk, __strcpy_chk, __memcpy_chk
swprintf __swprintf_chk
SyncCtl __SyncCtl_chk
SyncCtl_r __SyncCtl_r_chk
syslog __syslog_chk
ThreadCtl __ThreadCtl_chk
ThreadCtlExt __ThreadCtlExt_chk
ThreadCtlExt_r __ThreadCtlExt_r_chk
ThreadCtl_r __ThreadCtl_r_chk
vasprintf __vasprintf_chk
vdprintf __vdprintf_chk
vfprintf __vfprintf_chk
vfwprintf __vfwprintf_chk
vprintf __vprintf_chk
vsnprintf __vsnprintf_chk
vsprintf __vsprintf_chk
vswprintf __vswprintf_chk
vsyslog __vsyslog_chk
vwprintf __vwprintf_chk
wcrtomb __wcrtomb_chk
wcscat __wcscat_chk
wcscpy __wcscpy_chk
wcsncat __wcsncat_chk
wctomb __wctomb_chk
wprintf __wprint_chk

Supported fortified system functions

QNX OS supports the following fortified system functions:

accept()
accept4()
asprintf()
c16rtomb()
c32rtomb()
confstr()
ConnectClientInfo()
ConnectClientInfo_r()
ConnectClientInfoAble()
devctl()
devctlv()
dprintf()
FD_CLR()
FD_SET()
fgets()
fgetws()
fprintf()
fread()
fwprintf()
getcwd()
getdelim()
getdomainname()
getgrouplist()
getgroups()
gethostname()
getline()
getlogin_r()
getpeername()
gets()
getsockname()
getsockopt()
getwd()
ioctl()
iofdinfo()
iofunc_client_info_able()
lio_listio()
lio_listio64()
mbsrtowcs()
mbstowcs()
memccpy()
memcpy()
memcpyv()
memmove()
memset()
memset_s()
mq_clockreceive()
mq_open()
mq_receive()
mq_timedreceive()
mq_timedreceive_monotonic()
MsgRead()
MsgRead_r()
MsgReadv()
MsgReadv_r()
MsgReceive()
MsgReceive_r()
MsgReceivePulse()
MsgReceivePulse_r()
MsgReceivev()
MsgReceivev_r()
MsgSend()
MsgSend_r()
MsgSendnc()
MsgSendnc_r()
MsgSendsv()
MsgSendsv_r()
MsgSendsvnc()
MsgSendsvnc_r()
MsgSendv()
MsgSendv_r()
MsgSendvnc()
MsgSendvnc_r()
MsgSendvs()
MsgSendvs_r()
MsgSendvsnc()
MsgSendvsnc_r()
open()
open64()
openat()
pathfind_r()
poll()
posix_devctl()
pread()
pread64()
printf()
pselect()
ptsname_r()
read()
readdir_r()
_readdir_r()
readdir64_r()
_readdir64_r()
readblock()
readcond()
readlink()
readlinkat()
readv()
realpath()
recv()
recvfrom()
recvmmsg()
recvmsg()
resmgr_msgget()
resmgr_msggetv()
resmgr_msgread()
resmgr_msgreadv()
resmgr_pathname()
SchedCtl()
SchedCtl_r()
select()
searchenv()
sem_open()
sendmmsg()
SETIOV()
snprintf()
sopen()
sprintf()
stpcpy()
stpncpy()
straddstr()
strcat()
strcpy()
strftime()
strlcat()
strlcpy()
strncat()
strncpy()
strxfrm()
swab()
swprintf()
SyncCtl()
SyncCtl_r()
syslog()
ThreadCtl()
ThreadCtl_r()
ThreadCtlExt()
ThreadCtlExt_r()
TraceEvent()
ttyname_r()
vsyslog()
vasprintf()
vdprintf()
vfprintf()
vfwprintf()
vprintf()
vsnprintf()
vsprintf()
vswprintf()
vsyslog()
vwprintf()
wcrtomb()
wcsftime()
wcscat()
wcscpy()
wcsncat()
wcsncpy()
wcsrtombs()
wcstombs()
wcsxfrm()
wctomb()
wmemcpy()
wmemmove()
wmemset()
wprintf()
Page updated: