Updated in 2024-08.
glibc 2.3.4 introduced _FORTIFY_SOURCE
in 2004
to catch security errors due to misuse of some C library functions. The
initially supported functions were
fprintf, gets, memcpy, memmove, mempcpy, memset, printf, snprintf, sprintf, stpcpy, strcat, strcpy, strncat, strncpy, vfprintf, vprintf, vsnprintf, vsprintf
and focused on buffer overflow detection and dangerous printf
%n
uses. The implementation leverages inline functions and
__builtin_object_size
(see [PATCH]
Object size checking to prevent (some) buffer overflows). More
functions were added over time and __builtin_constant_p
was
used as well. As of 2022-11 glibc defines 79 default version
*_chk
functions.
Let's walk through a simple C program to see how fortify source works with a modern glibc.
1 |
|
Compile this program with -D_FORTIFY_SOURCE=2
together
with optimization level -O1
or above.
Many headers transitively include features.h
. This
header checks fortify level and optimization level, and defines
__USE_FORTIFY_LEVEL
. 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Our program includes string.h
. This header provides a
declaration of strcpy
and includes
bits/string_fortified.h
. 1
2
3
4
5
6
7
8
9
10extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
__THROW __nonnull ((1, 2));
...
/* Functions with security checks. */
bits/string_fortified.h
defines 1
2
3
4
5__fortify_function char *
__NTH (strcpy (char *__restrict __dest, const char *__restrict __src))
{
return __builtin___strcpy_chk (__dest, __src, __glibc_objsize (__dest));
}
__fortify_function
is a macro which expands to
extern __inline __attribute__ ((__always_inline__)) __attribute__ ((__gnu_inline__)) __attribute__ ((__artificial__))
.
Therefore this declares an extern inline
GNU inline
function which is only used for inlining and is not compiled on its own
(no definition in the relocatable object file). The GNU attribute
gnu_inline
is also available in C++ mode and provides the
desired inlining semantics. (In LLVM, the function has the
available_externally
linkage, instead of having the
linkonce_odr
linkage in a COMDAT.) (Since there is an
external definition in libc, the extern inline
GNU inline
function is like an inline
C99 inline function.)
always_inline
tells the compiler to always inline a
direct call to the function. This is important for
__builtin_object_size
in the callee to know the argument
object.
When debug information is produced, artificial
marks the
function as an artificial entity (DW_AT_artificial
):
Any debugging information entry representing the declaration of an object or type artificially generated by a compiler and not explicitly declared by the source program may have a DW_AT_artificial attribute, which is a flag.
__glibc_objsize
uses a compiler built-in function to
determine the object size. 1
2
3
4
5
6
7
8
9
10
11
12
13/* Fortify support. */
/* Use __builtin_dynamic_object_size at _FORTIFY_SOURCE=3 when available. */
With _FORTIFY_SOURCE==2
,
strcpy(a, argv[1]);
expands to
__builtin___strcpy_chk(a, argv[1], __builtin_object_size(a, 1));
.
__builtin___strcpy_chk
lowers to either a
strcpy
or __strcpy_chk
function call,
depending whether the object size can be determined.
__strcpy_chk
is used if the destination size can be
determined to be a constant. __strcpy_chk
is defined in
debug/strcpy_chk.c
: 1
2
3
4
5
6
7
8
9
10/* Copy SRC to DEST with checking of destination buffer overflow. */
char *
__strcpy_chk (char *dest, const char *src, size_t destlen)
{
size_t len = strlen (src);
if (len >= destlen)
__chk_fail ();
return memcpy (dest, src, len + 1);
}
In the strcpy(a, argv[1]);
example, since
a
's size is known, the statement is like
__strcpy_chk(a, argv[1], 4);
.
strcpy
is used if the destination size cannot be
determined to be a constant. 1
void foo(char *b) { strcpy(b, a); }
In the GCC repository, run grep _chk gcc/builtins.def
to
get the list of builtin functions for fortify source.
_FORTIFY_SOURCE=1
vs _FORTIFY_SOURCE=2
1 | struct A { |
_FORTIFY_SOURCE=1
:__strcpy_chk(&g.b.a[1], b, 11)
_FORTIFY_SOURCE=2
:__strcpy_chk(&g.b.a[1], b, 3)
See the GCC documentation about the difference: Object Size Checking Built-in Functions.
_FORTIFY_SOURCE=3
Clang 8 implemented a
new builtin function __builtin_dynamic_object_size
. glibc
added support on 2021-12-31 and early 2022 (milestone: 2.33). See Introduce
_FORTIFY_SOURCE=3 and follow-ups.
GCC ported the feature in 2021-12 and glibc added support for GCC 12 on 2022-01-12. Interestingly, this is a glibc feature that Clang support happened before GCC.
1 | void *foo(size_t a, size_t b, const void *src, size_t n) { |
The memcpy
call lowers to
__memcpy_chk(buf, src, n, a * b)
where a * b
is not a constant. There is no multiplication overflow check for
a * b
.
See That
is not a number, that is a freed object for a bug-prone realloc
pattern discovered by _FORTIFY_SOURCE=3
.
GCC's
new fortification level: The gains and costs mentioned that
_FORTIFY_SOURCE=3
improved fortification by nearly 4
times.
Some projects ignore user-specified CFLAGS and does
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2
. User-specified
CFLAGS can defeat them by using -Wp,_FORTIFY_SOURCE
. There
will be a warning which can be suppressed with
-Wno-macro-redefined
in Clang while GCC needs a big hammer
-w
.
Compiler diagnostics
Some buffer overflow issues can be determined at compile time (both the source and destination sizes are known).
-Wstringop-overflow
-Wfortify-source
GCC provides a warning -Wstringop-overflow
.
Clang -Wfortify-source
is similar. For historical
reasons, some functions like snprintf
may use -Wbuiltin-memcpy-chk-size
.
1 | char a[4]; |
glibc has annotated many functions with the
access
attribute (see BZ
#25219). These attributes help GCC know the accessed number of
elements for pointer parameters and catch bugs at compile time.
Android bionic annotates standard library functions with
__attribute__((diagnose_if(...)))
to provide the
diagnostics, but the diagnostics cannot provide size information.
__builtin_va_arg_pack, __builtin_va_arg_pack_len
When the two builtin functions are available,
__va_arg_pack
is defined and some functions like
printf
are fortified as well. As Clang's documentation
indicates, the two builtin functions are not implemented.
1 | __fortify_function int |
With _FORTIFY_SOURCE >= 2
, %n
causes
*** %n in writable segment detected ***
with a if
__fmt
is in a writable segment. On Linux,
/proc/self/maps
is scanned
(sysdeps/unix/sysv/linux/readonly-area.c
).
Clang support
Clang supports many of the builtin string functions. The support is
good since https://reviews.llvm.org/D71082 and providing a builtin
function __warn_memset_zero_len
as a glibc workaround.
On the other hand, it tries to provide a more principled set of
language extensions to make a better fortify source implementation. See
pass_object_size,
pass_dynamic_object_size. The two attributes can be used together
with overloadable
and diagnose_if
. See The
Anatomy of Clang FORTIFY. glibc 2.40 introduced
pass_object_info
style fortified source for certain
functions. fprintf
will be mangled as
_ZL7fprintfP8_IO_FILEU17pass_object_size1PKcz
.
glibc misc/sys/cdefs.h
uses C inline model specific
macros to check Clang support. This makes it difficult to undefine
__GNUC_STDC_INLINE__
__GNUC_GNU_INLINE__
in
C++ mode. See a GCC issue c-family/c-cppbuiltin.cc:
Undefine GNUC_STDC_INLINE in C++ mode?.
1 |
Flexible array member
Before C99 introduced flexible array member, many projects had used a
proto-flexible-array-member feature by abusing the last array member of
a structure. GCC and Clang have implemented workarounds for such code.
See __builtin_object_size(P->M,
1) where M is an array and the last member of a struct fails how the
workarounds make __builtin_object_size
prone to return -1
and defeat _FORTIFY_SOURCE
for some code.
Symbol interposition
Redirected symbol names require interposers to define more symbols.
E.g. umockdev needed to
define readlinkat
. pipewire wrapped openat
and chose to disable
_FORTIFY_SOURCE
locally.
_FORTIFY_SOURCE
vs
sanitizers
In terms of bug catching capability, _FORTIFY_SOURCE
does not perform as well as some dynamic instrumentation tools. There
are many cases it cannot handle. The buffer overflow checks can be
detected by AddressSanitizer and HWAddressSanitizer's interceptors as
well. Integer multiplication overflow (e.g. __fread_chk
)
can be detected if libc functions are instrumented with
UndefinedSanitizer. (For performance consideration, libc needs to
compile in multiple modes.) _FORTIFY_SOURCE
's strength lies
in its deployment convenience and very small overhead (code size,
performance, memory usage).
When a sanitizer is used, generally _FORTIFY_SOURCE
should be disabled since sanitizer runtime does not implement most
*_chk
functions. Using _FORTIFY_SOURCE
will
regress error checking (asan/hwasan/tsan) or cause false positives
(msan).
I think it will be interesting if someone can compare
_FORTIFY_SOURCE
with some mature static analyzers.
Adoption
- Fedora Core 4 started to enable
_FORTIFY_SOURCE
. - Gentoo https://bugs.gentoo.org/259417. The hardened profile adopted
_FORTIFY_SOURCE=3
for GCC in 2023-01. Clang tracker bug https://bugs.gentoo.org/851111 - SUSE Linux 10.0 started to enable
_FORTIFY_SOURCE=2
.
Alternative implementations
https://git.2f30.org/fortify-headers/ is a standalone implementation. It is libc-agnostic and overlays the system headers.
NetBSD implemented _FORTIFY_SOURCE
in 2006-11.
(Add a BSD-licensed re-implementation of the gcc-4.1 libssp.
)
In 2017, newlib ported
the NetBSD implementation.
Since 2007, Apple's Libc has included an implementation in the
secure/
directory.
ChromeOS patches glibc to use Clang-style
_FORTIFY_SOURCE
which is based on the
pass_object_info
attribute. For more information, see https://crbug.com/638456.
Android
bionic added GCC-style _FORTIFY_SOURCE
in 2012-06.
It gained Clang-style _FORTIFY_SOURCE
support in 2017-02
and removed GCC-style _FORTIFY_SOURCE
support in 2018-07.
Linux kernel introduced CONFIG_FORTIFY_SOURCE
in 2017-07.
Use CONFIG_FORTIFY_SOURCE=y
.
Miscellaneous
For sanitizers using interceptors, do not use
_FORTIFY_SOURCE
. See All
about sanitizer interceptors#_FORTIFY_SOURCE.
https://github.com/siddhesh/fortify-metrics estimates how much of a program or project is fortified.
https://github.com/serge-sans-paille/fortify-test-suite provides a test suite.
A -D_FORTIFY_SOURCE=2
compile may optimize more than a
-U_FORTIFY_SOURCE
one (https://github.com/llvm/llvm-project/issues/60389). With
-D_FORTIFY_SOURCE=2
, Clang knows the first argument is
non-null and may make corresponding optimization. With
-U_FORTIFY_SOURCE
, Clang lowers memset
to
llvm.memset.p0.i64
which does
not capitalize the nonnull
attribute.
1
2
3
4
5
6
7
8extern void *memset (void *__s, int __c, size_t __n) noexcept (true) __attribute__ ((__nonnull__ (1)));
...
extern __inline __attribute__ ((__always_inline__)) __attribute__ ((__gnu_inline__)) __attribute__ ((__artificial__)) void *
memset (void *__dest, int __ch, size_t __len) noexcept (true)
{
return __builtin___memset_chk (__dest, __ch, __len,
__builtin_object_size (__dest, 0));
}