For anybody unfamiliar with this, as I was, this appears to refer to Intel's Indirect Branch Tracking feature[1] (and the equivalent on ARM, BTI). The idea is that an indirect branch can only pass control to a location that starts with an "end branch" instruction. An indirect branch is one that jumps to a location whose value is loaded or computed from either a register or memory address: think calling a function pointer in C.
Without IBT, you'd have this equivalence between C and assembly:
main() {
void (*f)();
f = foo;
f();
}
void foo() { }
---
main:
movl $foo, %edx
call *%edx
ret
foo:
ret
If IBT is enabled, the above code triggers an exception because foo doesn't begin with an "end branch" instruction. When IBT is enabled by the compiler, the above code gets assembled as:
main:
endbr64
movl $foo, %edx
call *%edx
ret
foo:
endbr64
ret
Now the compiler inserts endbr64 at the start of each function prologue. The reason for this feature, is to use as a defense in depth against JOP, and COP attacks, as it means that the only "widgets" available to you are entire functions, which can be far harder to exploit and chain.
[1]: https://www.intel.com/content/dam/develop/external/us/en/doc...
Without IBT, you'd have this equivalence between C and assembly:
If IBT is enabled, the above code triggers an exception because foo doesn't begin with an "end branch" instruction. When IBT is enabled by the compiler, the above code gets assembled as: Now the compiler inserts endbr64 at the start of each function prologue. The reason for this feature, is to use as a defense in depth against JOP, and COP attacks, as it means that the only "widgets" available to you are entire functions, which can be far harder to exploit and chain.[1]: https://www.intel.com/content/dam/develop/external/us/en/doc...