Engineers from Apple have announced the readiness for testing the '-fbounds-safety' mode for the Clang compiler, which provides guarantees for safe operations with buffers in C code. This mode is included in the LLVM fork supported by Apple for the Swift project. There are plans for a gradual integration of the '-fbounds-safety' functionality into the main LLVM/Clang codebase.
It is noted that the proposed protection mechanism is already actively used in Apple products, such as the XNU kernel, firmware, audio libraries, and image decoders. Enabling the '-fbounds-safety' mode reduces application performance by an average of 5% (with a range from -1% to 29%), increases the code size by 9.1% (with a range from -1.4% to 38%), and slows down compilation by 11%.
Using the '-fbounds-safety' mode for automatically detecting out-of-bounds accesses related to pointers requires the addition of specific annotations to the code and the inclusion of the header file 'ptrcheck.h'. The essence of the proposed protection method lies in automatically attaching checks to ensure compliance with permissible bounds, which are added based on manually specified annotations or sizes known to the compiler.
Unlike the use of wide pointers in the code, which contain information about the upper and lower bounds of the buffer along with the address, using the '-fbounds-safety' mode does not break the ABI (Application Binary Interface), does not change the format of exported pointers, and does not require a complete overhaul of the entire project. In the '-fbounds-safety' mode, wide pointers are only applied in areas that do not intersect with the ABI, while regular pointers with boundary-check substitutions, based on annotations containing boundary information, are applied for pointers that affect the ABI.
Annotations must be attached to pointers in struct fields and function parameters that reference arrays of objects, as well as to global variables with pointers. For pointers in local variables, annotations do not need to be added, as they are automatically handled as wide pointers already including information about permissible bounds. Hints about constructs in the code requiring annotations are provided by the compiler when run with the '-fbounds-safety' flag.
The protection model based on "-fbounds-safety" can be implemented gradually, file by file, without interrupting the development of the entire project. Adding protection to a project involves specifying annotations in a particular code file, addressing compiler warnings, and performing program testing, after which these steps are repeated for the next file. The code with added annotations remains compatible with regular C code and compilers that do not support "-fbounds-safety" (when built with other compilers or without the "-fbounds-safety" flag, no additional boundary checks will be included).
During program execution, if an out-of-bounds access is detected, an exception is generated and the program terminates. An emergency termination may also occur if incorrect annotations are specified, so additional testing of the program is necessary when using "-fbounds-safety."
In the example below, the parameter "int *p" has the annotation "__counted_by(n)" added, which provides an additional runtime check for valid boundaries. If you try to compile the code in "-fbounds-safety" mode without specifying this annotation, the compiler will issue a warning about the absence of boundary information for the array while processing the expression "p[i] = 0". #include void init_buf(int *__counted_by(n) p, int n) { for (int i = 0; i < n; ++i) p[i] = 0; // In "-fbounds-safety" mode, the compiler will automatically insert a check akin to the code "if (i = n) trap();" }
For local variables with pointers, checks are automatically attached, for example: void foo(int i){ char *buf = (char *)malloc(10); // Information about boundaries will be saved for the pointer buf buf[i] = 0xff; // A check "if (buf + i = buf + 10) trap();" will be automatically inserted }
The compiler optimizes whenever possible, excluding unnecessary code if the necessary checks are already present in the code. For example: for (size_t i = 0; i < count; ++i) { buf[i] = i; // The check "if (i = count) trap()" will not be added, as there is already a condition "i < count" and i cannot be less than 0. }
Main annotations:
- "__counted_by(N)" — defines the size of the buffer in elements of the target type.
- "__sized_by(N)" — defines the size of the buffer in bytes.
- » __ended_by(P)» — sets the upper limit of the buffer.
- «__null_terminated» — considers the null character as the end of the buffer.
- «__single» — binds the pointer to a single object. This is the default for pointers that affect the ABI unless the annotation is explicitly set.
- «__bidi_indexable» — extended pointer with information about upper and lower bounds. This is the default for pointers that do not affect the ABI.
- «__indexable» — extended pointer with information about the upper bound.
- «__unsafe_indexable» — pointer without boundary checks (for portability with unsafe code, such as obtaining pointers from external code).
Source: opennet.ru
