golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
121.11k stars 17.37k forks source link

cmd/asm,cmd/compile: add support for control flow integrity #66054

Open 4a6f656c opened 4 months ago

4a6f656c commented 4 months ago

Modern CPUs provide support for branch tracking control flow integrity (BTCFI) in the form of Intel's Indirect Branch Tracking (IBT) and AARCH64's Branch Tracking Identification (BTI). BTCFI helps to mitigate flow control based exploits, such as those achieved via Jump Oriented Programming (JOP).

Some operating systems (for example, OpenBSD 7.4 onwards) are starting to enforce IBT and BTI on hardware where it is available (Intel Tigerlake onwards, Apple's M2, etc). Go binaries need to be marked with PT_OPENBSD_NOBTCFI in order to allow them to execute. Other operating systems have varying levels of support (Linux enables IBT in the kernel, but not userland AIUI - https://lwn.net/Articles/889475/).

Go should ideally support BTCFI - in the IBT/BTI case it is effectively a matter of providing a "landing pad" instruction (endbr64 on amd64 and bti c on arm64), at function entry points and any other locations where control flow is expected to land. On machines that do not support these instructions (or have BTCFI enabled/enforced in userland), they are effectively a no-ops. This does potentially impact code where it is intentional to jump into a function at some offset - for example, duff devices (DUFFCOPY, DUFFZERO) and jump tables. This would also need landing pads at each potential entry point, or revisiting/disabling.

gopherbot commented 4 months ago

Change https://go.dev/cl/568435 mentions this issue: cmd/link,debug/elf: mark Go binaries with no branch target CFI on openbsd

Jorropo commented 4 months ago

How much good enough are you asking for this to be ? For comparison don't even enable ASLR by default. (and -buildmode=pie greatly increase build sizes) Due to goroutine preamption my first guess would be that all instructions would need to be marked as valid jump destinations, making the value very dubious. Even if we only mark safe points and expected jump destinations that still a lot of places.

bjorndm commented 2 months ago

BTCFU is a palliative for C and C++ which allow ROP due to their deficient design and allowance for undefined behavior.

If one doesn't use unsafe, then ROP should not be possible in Go. This might negatively affect the performance of Go programs and is another example of how non C programming languages have to pay "C taxes" as it were. This should certainly remain optional.

Furthermore I think it is crazy that now we have to introduce a "come from" assembly instruction just because of the critical design flaws of C. Rather, we should be working on getting rid of C everywhere, including in OS.

4a6f656c commented 1 week ago

I have a branch that adds branch tracking control flow integrity (BTCFI) to Go for arm64 (in the form of BTI) and amd64 (in the form of IBT).

4a6f656c commented 1 week ago

Due to goroutine preamption my first guess would be that all instructions would need to be marked as valid jump destinations, making the value very dubious. Even if we only mark safe points and expected jump destinations that still a lot of places.

BTCFI generally only tracks indirect branches. In the case of asynchronous preemption there is no change for amd64, as it resumes via RET which is pulling the address off the stack (and is permissible with IBT enforced). In the case of arm64, the current code is using JMP (R27) which is an indirect branch and is not permissible with BTI enforced - this is however easily fixed via the use of RET with a specific register.

Jorropo commented 1 week ago

@4a6f656c thx that answers I had and couldn't understand from your code. I thought RET would be considered an indirect branch. But then does that means IBT does not do anything against Return-Oriented-Programing ?

4a6f656c commented 1 week ago

If one doesn't use unsafe, then ROP should not be possible in Go. This might negatively affect the performance of Go programs and is another example of how non C programming languages have to pay "C taxes" as it were. This should certainly remain optional.

This also implies that there are no bugs in Go's compiler or runtime, that C code is never called (hint, don't use Go on macOS) and that Go is never called from C code (because that means the C code cannot have branch tracking enforced, since the Go code does not support it).

Furthermore I think it is crazy that now we have to introduce a "come from" assembly instruction just because of the critical design flaws of C. Rather, we should be working on getting rid of C everywhere, including in OS.

It's not a "come from", rather a "it is permissible to indirect branch to here" instruction.

Jorropo commented 1 week ago

Add assembly routines to the list of things not to use to never have ROPs. Which are used on literally all platforms by the runtime and some third packages.

4a6f656c commented 1 week ago

@4a6f656c thx that answers I had and couldn't understand from your code. I thought RET would be considered an indirect branch. But then does that means IBT does not do anything against Return-Oriented-Programing ?

Right, it is forward-edge flow control rather than reverse-edge flow control (the original description should have said Jump-Oriented Programming, which I've fixed).