_FORTIFY_SOURCE
2022-11-6 15:0:0 Author: maskray.me(查看原文) 阅读量:15 收藏

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 was fprintf, gets, memcpy, memmove, mempcpy, memset, printf, snprintf, sprintf, stpcpy, strcat, strcpy, strncat, strncpy, vfprintf, vprintf, vsnprintf, vsprintf and focused on buffer overflow 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
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <string.h>

char a[4];

int main(int argc, char *argv[]) {
strcpy(a, argv[1]);
puts(a);
}

Compile this program with -D_FORTIFY_SOURCE=2 together with optimization level -O1 or above.

features.h 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
#if defined _FORTIFY_SOURCE && _FORTIFY_SOURCE > 0
# if !defined __OPTIMIZE__ || __OPTIMIZE__ <= 0
# warning _FORTIFY_SOURCE requires compiling with optimization (-O)
# elif !__GNUC_PREREQ (4, 1)
# warning _FORTIFY_SOURCE requires GCC 4.1 or later
# elif _FORTIFY_SOURCE > 2 && (__glibc_clang_prereq (9, 0) \
|| __GNUC_PREREQ (12, 0))

# if _FORTIFY_SOURCE > 3
# warning _FORTIFY_SOURCE > 3 is treated like 3 on this platform
# endif
# define __USE_FORTIFY_LEVEL 3
# elif _FORTIFY_SOURCE > 1
# if _FORTIFY_SOURCE > 2
# warning _FORTIFY_SOURCE > 2 is treated like 2 on this platform
# endif
# define __USE_FORTIFY_LEVEL 2
# else
# define __USE_FORTIFY_LEVEL 1
# endif
#endif
#ifndef __USE_FORTIFY_LEVEL
# define __USE_FORTIFY_LEVEL 0
#endif

string.h provides a declaration of strcpy and includes bits/string_fortified.h.

1
2
3
4
5
6
7
8
9
10
extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
__THROW __nonnull ((1, 2));
...

#if __GNUC_PREREQ (3,4)
# if __USE_FORTIFY_LEVEL > 0 && defined __fortify_function

# include <bits/string_fortified.h>
# endif
#endif

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 expands to extern __inline __attribute__ ((__always_inline__)) __attribute__ ((__gnu_inline__)) __attribute__ ((__artificial__)). This is 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). gnu_inline is also available in C++ mode and provides the desired behavior. (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

#define __bos(ptr) __builtin_object_size (ptr, __USE_FORTIFY_LEVEL > 1)
#define __bos0(ptr) __builtin_object_size (ptr, 0)


#if __USE_FORTIFY_LEVEL == 3 && (__glibc_clang_prereq (9, 0) \
|| __GNUC_PREREQ (12, 0))
# define __glibc_objsize0(__o) __builtin_dynamic_object_size (__o, 0)
# define __glibc_objsize(__o) __builtin_dynamic_object_size (__o, 1)
#else
# define __glibc_objsize0(__o) __bos0 (__o)
# define __glibc_objsize(__o) __bos (__o)
#endif

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

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
2
3
4
5
6
struct A {
struct { char a[4]; int x; } b;
char c[4];
} g;

void foo(char *b) { strcpy(&g.b.a[1], b); }
  • _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. 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
2
3
4
5
void *foo(size_t a, size_t b, const void *src, size_t n) {
void *buf = malloc(a * b);
memcpy(buf, src, n);
return buf;
}

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
2
char a[4];
void foo(void) { strcpy(a, "abcd"); }

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.

__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
2
3
4
5
__fortify_function int
printf (const char *__restrict __fmt, ...)
{
return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
}

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 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
2
3
4
5
6
7
8
9
10
11
12
#if (!defined __cplusplus || __GNUC_PREREQ (4,3) \
|| (defined __clang__ && (defined __GNUC_STDC_INLINE__ \
|| defined __GNUC_GNU_INLINE__)))
# if defined __GNUC_STDC_INLINE__ || defined __cplusplus
# define __extern_inline extern __inline __attribute__ ((__gnu_inline__))
# define __extern_always_inline \
extern __always_inline __attribute__ ((__gnu_inline__))
# else
# define __extern_inline extern __inline
# define __extern_always_inline extern __always_inline
# endif
#endif

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 implemented 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

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.)

ChromeOS patches glibc to use Clang-style _FORTIFY_SOURCE which is based on the pass_object_info attribute. 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

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.


文章来源: https://maskray.me/blog/2022-11-05-fortify-source
如有侵权请联系:admin#unsafe.sh