Message ID | 20201204230615.2392-6-dbuono@linux.vnet.ibm.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add support for Control-Flow Integrity | expand |
On 201204 1806, Daniele Buono wrote: > Document how to compile with CFI and how to maintain CFI-safe code > > Signed-off-by: Daniele Buono <dbuono@linux.vnet.ibm.com> Reviewed-by: Alexander Bulekov <alxndr@bu.edu> Thanks > --- > docs/devel/control-flow-integrity.rst | 137 ++++++++++++++++++++++++++ > 1 file changed, 137 insertions(+) > create mode 100644 docs/devel/control-flow-integrity.rst > > diff --git a/docs/devel/control-flow-integrity.rst b/docs/devel/control-flow-integrity.rst > new file mode 100644 > index 0000000000..ec54d16a42 > --- /dev/null > +++ b/docs/devel/control-flow-integrity.rst > @@ -0,0 +1,137 @@ > +============================ > +Control-Flow Integrity (CFI) > +============================ > + > +This document describes the current control-flow integrity (CFI) mechanism in > +QEMU. How it can be enabled, its benefits and deficiencies, and how it affects > +new and existing code in QEMU > + > +Basics > +------ > + > +CFI is a hardening technique that focusing on guaranteeing that indirect > +function calls have not been altered by an attacker. > +The type used in QEMU is a forward-edge control-flow integrity that ensures > +function calls performed through function pointers, always call a "compatible" > +function. A compatible function is a function with the same signature of the > +function pointer declared in the source code. > + > +This type of CFI is entirely compiler-based and relies on the compiler knowing > +the signature of every function and every function pointer used in the code. > +As of now, the only compiler that provides support for CFI is Clang. > + > +CFI is best used on production binaries, to protect against unknown attack > +vectors. > + > +In case of a CFI violation (i.e. call to a non-compatible function) QEMU will > +terminate abruptly, to stop the possible attack. > + > +Building with CFI > +----------------- > + > +NOTE: CFI requires the use of link-time optimization. Therefore, when CFI is > +selected, LTO will be automatically enabled. > + > +To build with CFI, the minimum requirement is Clang 6+. If you > +are planning to also enable fuzzing, then Clang 11+ is needed (more on this > +later). > + > +Given the use of LTO, a version of AR that supports LLVM IR is required. > +The easies way of doing this is by selecting the AR provided by LLVM:: > + > + AR=llvm-ar-9 CC=clang-9 CXX=lang++-9 /path/to/configure --enable-cfi > + > +CFI is enabled on every binary produced. > + > +If desired, an additional flag to increase the verbosity of the output in case > +of a CFI violation is offered (``--enable-debug-cfi``). > + > +Using QEMU built with CFI > +------------------------- > + > +A binary with CFI will work exactly like a standard binary. In case of a CFI > +violation, the binary will terminate with an illegal instruction signal. > + > +Incompatible code with CFI > +-------------------------- > + > +As mentioned above, CFI is entirely compiler-based and therefore relies on > +compile-time knowledge of the code. This means that, while generally supported > +for most code, some specific use pattern can break CFI compatibility, and > +create false-positives. The two main patterns that can cause issues are: > + > +* Just-in-time compiled code: since such code is created at runtime, the jump > + to the buffer containing JIT code will fail. > + > +* Libraries loaded dynamically, e.g. with dlopen/dlsym, since the library was > + not known at compile time. > + > +Current areas of QEMU that are not entirely compatible with CFI are: > + > +1. TCG, since the idea of TCG is to pre-compile groups of instructions at > + runtime to speed-up interpretation, quite similarly to a JIT compiler > + > +2. TCI, where the interpreter has to interpret the generic *call* operation > + > +3. Plugins, since a plugin is implemented as an external library > + > +4. Modules, since they are implemented as an external library > + > +5. Directly calling signal handlers from the QEMU source code, since the > + signal handler may have been provided by an external library or even plugged > + at runtime. > + > +Disabling CFI for a specific function > +------------------------------------- > + > +If you are working on function that is performing a call using an > +incompatible way, as described before, you can selectively disable CFI checks > +for such function by using the decorator ``QEMU_DISABLE_CFI`` at function > +definition, and add an explanation on why the function is not compatible > +with CFI. An example of the use of ``QEMU_DISABLE_CFI`` is provided here:: > + > + /* > + * Disable CFI checks. > + * TCG creates binary blobs at runtime, with the transformed code. > + * A TB is a blob of binary code, created at runtime and called with an > + * indirect function call. Since such function did not exist at compile time, > + * the CFI runtime has no way to verify its signature and would fail. > + * TCG is not considered a security-sensitive part of QEMU so this does not > + * affect the impact of CFI in environment with high security requirements > + */ > + QEMU_DISABLE_CFI > + static inline tcg_target_ulong cpu_tb_exec(CPUState *cpu, TranslationBlock *itb) > + > +NOTE: CFI needs to be disabled at the **caller** function, (i.e. a compatible > +cfi function that calls a non-compatible one), since the check is performed > +when the function call is performed. > + > +CFI and fuzzing > +--------------- > + > +There is generally no advantage of using CFI and fuzzing together, because > +they target different environments (production for CFI, debug for fuzzing). > + > +CFI could be used in conjunction with fuzzing to identify a broader set of > +bugs that may not end immediately in a segmentation fault or triggering > +an assertion. However, other sanitizers such as address and ub sanitizers > +can identify such bugs in a more precise way than CFI. > + > +There is, however, an interesting use case in using CFI in conjunction with > +fuzzing, that is to make sure that CFI is not triggering any false positive > +in remote-but-possible parts of the code. > + > +CFI can be enabled with fuzzing, but with some caveats: > +1. Fuzzing relies on the linker performing function wrapping at link-time. > +The standard BFD linker does not support function wrapping when LTO is > +also enabled. The workaround is to use LLVM's lld linker. > +2. Fuzzing also relies on a custom linker script, which is only supported by > +lld with version 11+. > + > +In other words, to compile with fuzzing and CFI, clang 11+ is required, and > +lld needs to be used as a linker:: > + > + AR=llvm-ar-11 CC=clang-11 CXX=lang++-11 /path/to/configure --enable-cfi \ > + -enable-fuzzing --extra-ldflags="-fuse-ld=lld" > + > +and then, compile the fuzzers as usual. > -- > 2.17.1 > >
On 05/12/20 00:06, Daniele Buono wrote: > Document how to compile with CFI and how to maintain CFI-safe code > > Signed-off-by: Daniele Buono <dbuono@linux.vnet.ibm.com> > --- > docs/devel/control-flow-integrity.rst | 137 ++++++++++++++++++++++++++ > 1 file changed, 137 insertions(+) > create mode 100644 docs/devel/control-flow-integrity.rst You would have to add the file to docs/devel/index.rst as well. I'll do it. Paolo > diff --git a/docs/devel/control-flow-integrity.rst b/docs/devel/control-flow-integrity.rst > new file mode 100644 > index 0000000000..ec54d16a42 > --- /dev/null > +++ b/docs/devel/control-flow-integrity.rst > @@ -0,0 +1,137 @@ > +============================ > +Control-Flow Integrity (CFI) > +============================ > + > +This document describes the current control-flow integrity (CFI) mechanism in > +QEMU. How it can be enabled, its benefits and deficiencies, and how it affects > +new and existing code in QEMU > + > +Basics > +------ > + > +CFI is a hardening technique that focusing on guaranteeing that indirect > +function calls have not been altered by an attacker. > +The type used in QEMU is a forward-edge control-flow integrity that ensures > +function calls performed through function pointers, always call a "compatible" > +function. A compatible function is a function with the same signature of the > +function pointer declared in the source code. > + > +This type of CFI is entirely compiler-based and relies on the compiler knowing > +the signature of every function and every function pointer used in the code. > +As of now, the only compiler that provides support for CFI is Clang. > + > +CFI is best used on production binaries, to protect against unknown attack > +vectors. > + > +In case of a CFI violation (i.e. call to a non-compatible function) QEMU will > +terminate abruptly, to stop the possible attack. > + > +Building with CFI > +----------------- > + > +NOTE: CFI requires the use of link-time optimization. Therefore, when CFI is > +selected, LTO will be automatically enabled. > + > +To build with CFI, the minimum requirement is Clang 6+. If you > +are planning to also enable fuzzing, then Clang 11+ is needed (more on this > +later). > + > +Given the use of LTO, a version of AR that supports LLVM IR is required. > +The easies way of doing this is by selecting the AR provided by LLVM:: > + > + AR=llvm-ar-9 CC=clang-9 CXX=lang++-9 /path/to/configure --enable-cfi > + > +CFI is enabled on every binary produced. > + > +If desired, an additional flag to increase the verbosity of the output in case > +of a CFI violation is offered (``--enable-debug-cfi``). > + > +Using QEMU built with CFI > +------------------------- > + > +A binary with CFI will work exactly like a standard binary. In case of a CFI > +violation, the binary will terminate with an illegal instruction signal. > + > +Incompatible code with CFI > +-------------------------- > + > +As mentioned above, CFI is entirely compiler-based and therefore relies on > +compile-time knowledge of the code. This means that, while generally supported > +for most code, some specific use pattern can break CFI compatibility, and > +create false-positives. The two main patterns that can cause issues are: > + > +* Just-in-time compiled code: since such code is created at runtime, the jump > + to the buffer containing JIT code will fail. > + > +* Libraries loaded dynamically, e.g. with dlopen/dlsym, since the library was > + not known at compile time. > + > +Current areas of QEMU that are not entirely compatible with CFI are: > + > +1. TCG, since the idea of TCG is to pre-compile groups of instructions at > + runtime to speed-up interpretation, quite similarly to a JIT compiler > + > +2. TCI, where the interpreter has to interpret the generic *call* operation > + > +3. Plugins, since a plugin is implemented as an external library > + > +4. Modules, since they are implemented as an external library > + > +5. Directly calling signal handlers from the QEMU source code, since the > + signal handler may have been provided by an external library or even plugged > + at runtime. > + > +Disabling CFI for a specific function > +------------------------------------- > + > +If you are working on function that is performing a call using an > +incompatible way, as described before, you can selectively disable CFI checks > +for such function by using the decorator ``QEMU_DISABLE_CFI`` at function > +definition, and add an explanation on why the function is not compatible > +with CFI. An example of the use of ``QEMU_DISABLE_CFI`` is provided here:: > + > + /* > + * Disable CFI checks. > + * TCG creates binary blobs at runtime, with the transformed code. > + * A TB is a blob of binary code, created at runtime and called with an > + * indirect function call. Since such function did not exist at compile time, > + * the CFI runtime has no way to verify its signature and would fail. > + * TCG is not considered a security-sensitive part of QEMU so this does not > + * affect the impact of CFI in environment with high security requirements > + */ > + QEMU_DISABLE_CFI > + static inline tcg_target_ulong cpu_tb_exec(CPUState *cpu, TranslationBlock *itb) > + > +NOTE: CFI needs to be disabled at the **caller** function, (i.e. a compatible > +cfi function that calls a non-compatible one), since the check is performed > +when the function call is performed. > + > +CFI and fuzzing > +--------------- > + > +There is generally no advantage of using CFI and fuzzing together, because > +they target different environments (production for CFI, debug for fuzzing). > + > +CFI could be used in conjunction with fuzzing to identify a broader set of > +bugs that may not end immediately in a segmentation fault or triggering > +an assertion. However, other sanitizers such as address and ub sanitizers > +can identify such bugs in a more precise way than CFI. > + > +There is, however, an interesting use case in using CFI in conjunction with > +fuzzing, that is to make sure that CFI is not triggering any false positive > +in remote-but-possible parts of the code. > + > +CFI can be enabled with fuzzing, but with some caveats: > +1. Fuzzing relies on the linker performing function wrapping at link-time. > +The standard BFD linker does not support function wrapping when LTO is > +also enabled. The workaround is to use LLVM's lld linker. > +2. Fuzzing also relies on a custom linker script, which is only supported by > +lld with version 11+. > + > +In other words, to compile with fuzzing and CFI, clang 11+ is required, and > +lld needs to be used as a linker:: > + > + AR=llvm-ar-11 CC=clang-11 CXX=lang++-11 /path/to/configure --enable-cfi \ > + -enable-fuzzing --extra-ldflags="-fuse-ld=lld" > + > +and then, compile the fuzzers as usual. >
diff --git a/docs/devel/control-flow-integrity.rst b/docs/devel/control-flow-integrity.rst new file mode 100644 index 0000000000..ec54d16a42 --- /dev/null +++ b/docs/devel/control-flow-integrity.rst @@ -0,0 +1,137 @@ +============================ +Control-Flow Integrity (CFI) +============================ + +This document describes the current control-flow integrity (CFI) mechanism in +QEMU. How it can be enabled, its benefits and deficiencies, and how it affects +new and existing code in QEMU + +Basics +------ + +CFI is a hardening technique that focusing on guaranteeing that indirect +function calls have not been altered by an attacker. +The type used in QEMU is a forward-edge control-flow integrity that ensures +function calls performed through function pointers, always call a "compatible" +function. A compatible function is a function with the same signature of the +function pointer declared in the source code. + +This type of CFI is entirely compiler-based and relies on the compiler knowing +the signature of every function and every function pointer used in the code. +As of now, the only compiler that provides support for CFI is Clang. + +CFI is best used on production binaries, to protect against unknown attack +vectors. + +In case of a CFI violation (i.e. call to a non-compatible function) QEMU will +terminate abruptly, to stop the possible attack. + +Building with CFI +----------------- + +NOTE: CFI requires the use of link-time optimization. Therefore, when CFI is +selected, LTO will be automatically enabled. + +To build with CFI, the minimum requirement is Clang 6+. If you +are planning to also enable fuzzing, then Clang 11+ is needed (more on this +later). + +Given the use of LTO, a version of AR that supports LLVM IR is required. +The easies way of doing this is by selecting the AR provided by LLVM:: + + AR=llvm-ar-9 CC=clang-9 CXX=lang++-9 /path/to/configure --enable-cfi + +CFI is enabled on every binary produced. + +If desired, an additional flag to increase the verbosity of the output in case +of a CFI violation is offered (``--enable-debug-cfi``). + +Using QEMU built with CFI +------------------------- + +A binary with CFI will work exactly like a standard binary. In case of a CFI +violation, the binary will terminate with an illegal instruction signal. + +Incompatible code with CFI +-------------------------- + +As mentioned above, CFI is entirely compiler-based and therefore relies on +compile-time knowledge of the code. This means that, while generally supported +for most code, some specific use pattern can break CFI compatibility, and +create false-positives. The two main patterns that can cause issues are: + +* Just-in-time compiled code: since such code is created at runtime, the jump + to the buffer containing JIT code will fail. + +* Libraries loaded dynamically, e.g. with dlopen/dlsym, since the library was + not known at compile time. + +Current areas of QEMU that are not entirely compatible with CFI are: + +1. TCG, since the idea of TCG is to pre-compile groups of instructions at + runtime to speed-up interpretation, quite similarly to a JIT compiler + +2. TCI, where the interpreter has to interpret the generic *call* operation + +3. Plugins, since a plugin is implemented as an external library + +4. Modules, since they are implemented as an external library + +5. Directly calling signal handlers from the QEMU source code, since the + signal handler may have been provided by an external library or even plugged + at runtime. + +Disabling CFI for a specific function +------------------------------------- + +If you are working on function that is performing a call using an +incompatible way, as described before, you can selectively disable CFI checks +for such function by using the decorator ``QEMU_DISABLE_CFI`` at function +definition, and add an explanation on why the function is not compatible +with CFI. An example of the use of ``QEMU_DISABLE_CFI`` is provided here:: + + /* + * Disable CFI checks. + * TCG creates binary blobs at runtime, with the transformed code. + * A TB is a blob of binary code, created at runtime and called with an + * indirect function call. Since such function did not exist at compile time, + * the CFI runtime has no way to verify its signature and would fail. + * TCG is not considered a security-sensitive part of QEMU so this does not + * affect the impact of CFI in environment with high security requirements + */ + QEMU_DISABLE_CFI + static inline tcg_target_ulong cpu_tb_exec(CPUState *cpu, TranslationBlock *itb) + +NOTE: CFI needs to be disabled at the **caller** function, (i.e. a compatible +cfi function that calls a non-compatible one), since the check is performed +when the function call is performed. + +CFI and fuzzing +--------------- + +There is generally no advantage of using CFI and fuzzing together, because +they target different environments (production for CFI, debug for fuzzing). + +CFI could be used in conjunction with fuzzing to identify a broader set of +bugs that may not end immediately in a segmentation fault or triggering +an assertion. However, other sanitizers such as address and ub sanitizers +can identify such bugs in a more precise way than CFI. + +There is, however, an interesting use case in using CFI in conjunction with +fuzzing, that is to make sure that CFI is not triggering any false positive +in remote-but-possible parts of the code. + +CFI can be enabled with fuzzing, but with some caveats: +1. Fuzzing relies on the linker performing function wrapping at link-time. +The standard BFD linker does not support function wrapping when LTO is +also enabled. The workaround is to use LLVM's lld linker. +2. Fuzzing also relies on a custom linker script, which is only supported by +lld with version 11+. + +In other words, to compile with fuzzing and CFI, clang 11+ is required, and +lld needs to be used as a linker:: + + AR=llvm-ar-11 CC=clang-11 CXX=lang++-11 /path/to/configure --enable-cfi \ + -enable-fuzzing --extra-ldflags="-fuse-ld=lld" + +and then, compile the fuzzers as usual.
Document how to compile with CFI and how to maintain CFI-safe code Signed-off-by: Daniele Buono <dbuono@linux.vnet.ibm.com> --- docs/devel/control-flow-integrity.rst | 137 ++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/devel/control-flow-integrity.rst