4.2.1.4 The cf-protection test

  Problem:  An attacker could compromise an unprotected binary
  Fix By:   Compiling with -fcf-protection=full
  Waive If: The application will not run on the latest Intel hardware
  Waive If: The application is built by a compiler that does not support CET
  
  Example:  FAIL: cf-protection test because only branch protection enabled
  Example:  FAIL: cf-protection test because only return protection enabled
  Example:  FAIL: cf-protection test because no protection enabled
  Example:  FAIL: cf-protection test because insufficient Control Flow sanitization
  Example:  FAIL: cf-protection test because no .note.gnu.property section = no control flow information
  Example:  FAIL: cf-protection test because CET enabling note missing
  Example:  FAIL: cf-protection test because control flow protection is not enabled

Intel have introduced a new security feature called CET to their Tiger Lake and newer cores:

https://www.intel.com/content/www/us/en/newsroom/opinion/intel-cet-answers-call-protect-common-malware-threats.html

This test checks to see that this feature is enabled. Normally this is done by compiling the code with the -fcf-protection or -fcf-protection=full command line option enabled. (The first form of the option is an alias for the second, but the second form is preferred as it explicitly shows that all of the control flow protection features are being enabled).

But if an application contains assembler code, or it is linked against a library that has not been built with the protection enabled, or it is built by a compiler that does not support CET then this test can fail.

The feature has to be enabled in the compiler as it involves inserting new instructions into the compiled code. The feature is also an all-or-nothing type proposition for any process. Either all of the code in the process must have been built to support CET - in which case the feature can be enabled - or if even a single component does not support CET then it must be disabled for the entire process.

In order to enforce this the compiler inserts a special note into compiled object files (the .note.gnu.property section referred to above). The note indicates that CET is supported, as well as details of the minimum x86 architecture revision needed and so on.

Then when the object files are linked together to create the executable the linker checks all of these notes, and if any object file or library is missing one then it does not put a note in the output executable. Alternatively if all of the object files (and libraries of course) do have notes, but one or more of them do not have the CET-is-enabled flag, then the linker copies the notes into the executable, but always clears the CET-is-enabled flag.

Finally when a program is executed the run-time loader checks this note and if the CET-is-enabled flag is present then it enables the CET feature in the hardware.

Fixing this check either means enabling the -fcf-protection=full (for gcc) or the -fcf-protection-branch and -fcf-protection-return options (for Clang).

If an assembler source file is used as part of an application then it too needs to be updated. Any location in the source code where an indirect branch or function call can land must now have either ENDBR64 (for 64-bit assembler) or ENDBR32 (for 32-bit assembler) as the first instruction executed.

In addition the assembler needs a note to indicate that it now supports CET. This note can be added via including this code snippet in the sources:

	.section	.note.gnu.property,"a"
	.align 8
	.long	 1f - 0f
	.long	 4f - 1f
	.long	 5
0:
	.string	 "GNU"
1:
	.align 8
	.long	 0xc0000002
	.long	 3f - 2f
2:
	.long	 0x3
3:
	.align 8
4:

If necessary the test can be disabled via the --skip-cf-protection option and re-enabled via the --test-cf-protection option.

For more information on CET see: https://www.intel.com/content/dam/develop/external/us/en/documents/catc17-introduction-intel-cet-844137.pdf