Lazy bindings
Lazy bindings is the method by which the dynamic linker resolves external symbols the first time the function is called. This improves a bit the startup performances for binaries with a lot of symbols.
In 1998, patching the GOT was publicly described in Rafal Wojtczuk’s paper, Defeating Solar Designer’s Non-executable Stack Patch.
Originally in OpenBSD to update a symbol’s location, the linker would resolve the
address, block signals, grab a spinlock, call mprotect
to make the GOT r/w
, update the entry, call
mprotect
again to make the GOT read-only, release the
spinlock, unblock signals.
In 2014, Philip Guenther presented a new solution at the EuroBSDCon 2014:
Secure (and hopefully efficient) Lazy
Binding:
A new syscall, kbind(void *addr, size_t len, void *data)
, that does the same thing as the previous process, but in
kernel-land. This has of course a lot of security
implications.
This feature landed in OpenBSD 5.9, in 2015, with the following counter
measures are in place to prevent malicious uses of such an attractive syscall:
kbind is currently intended for use by ld.so(1) only. It is therefore not provided as a function and two security checks are performed to bind it (pun intended) to its use in ld.so(1): the first time kbind is used, the kernel records both the text address of the call and the value of the cookie argument. If those values differ in a later kbind call, then the process is killed.
Unfortunately, being able to call kbind
is just a matter of leaking the
cookie, which is conveniently stored in the .openbsd.randomdata
section,
and to ret2ld in the middle of _dl_bind
, which is the function calling
kbind
. The aforementioned cookie is stored in an unsigned long
, usually
meaning 2^32
different values, which is a good amount of entropy.
Amusingly, this syscall is always allowed under pledge.
Lazy bindings are a thing from the past, with questionable performance benefits: nowadays every binary should resolve the symbols at startup, and mark them readonly. The musl libc doesn’t even support lazy bindings by design.
This mitigation adds a dangerous syscall allowing an attacker with arbitrary read and a call to write anywhere in the memory accessible by the current process to support a feature that shouldn’t exist anymore in 2019.