Fault Injection on Linux: Practical KERNELFAULT-Style Attacks

This post will detail my examination of the KERNELFAULT paper, which you can download here. In brief summary, the KERNELFAULT paper describes a methodology for injecting faults into a system running Linux, for the purpose of privilege escalation to root, as well as for controlling the program counter.

The code used in this attack can be downloaded here, within the “pi” folder.

I began my experiment by preparing a Raspberry Pi 4. I made a software reset puller (controlled via ChipWhisperer’s GPIO’s) and broke out the UART. I also removed the 1.2V decoupling capacitors on the other side of the PCB, opposite the Broadcom SoC. These are marked below, with purple and blue markings:

Conceptually, the path to root is clear:

  • We begin as a standard user
  • We set all our unused registers to 0
  • We execute a setresuid(0,0,0) system call
  • We induce a fault of some description within the setresuid call, causing the operating system to incorrectly assign us a privilege.
  • We check if we now have elevated privileges, and if so, return the user to a root shell.

Let us examine the setresuid call further, pasted below for reference incase it changes down the track:

* This function implements a generic ability to update ruid, euid,
* and suid. This allows you to implement the 4.4 compatible seteuid().
long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
struct user_namespace *ns = current_user_ns();
const struct cred *old;
struct cred *new;
int retval;
kuid_t kruid, keuid, ksuid;

kruid = make_kuid(ns, ruid);
keuid = make_kuid(ns, euid);
ksuid = make_kuid(ns, suid);

if ((ruid != (uid_t) -1) && !uid_valid(kruid))
return -EINVAL;

if ((euid != (uid_t) -1) && !uid_valid(keuid))
return -EINVAL;

if ((suid != (uid_t) -1) && !uid_valid(ksuid))
return -EINVAL;

new = prepare_creds();
if (!new)
return -ENOMEM;

old = current_cred();

retval = -EPERM;
if (!ns_capable_setid(old->user_ns, CAP_SETUID)) {
if (ruid != (uid_t) -1 && !uid_eq(kruid, old->uid) &&
!uid_eq(kruid, old->euid) && !uid_eq(kruid, old->suid))
goto error;
if (euid != (uid_t) -1 && !uid_eq(keuid, old->uid) &&
!uid_eq(keuid, old->euid) && !uid_eq(keuid, old->suid))
goto error;
if (suid != (uid_t) -1 && !uid_eq(ksuid, old->uid) &&
!uid_eq(ksuid, old->euid) && !uid_eq(ksuid, old->suid))
goto error;

if (ruid != (uid_t) -1) {
new->uid = kruid;
if (!uid_eq(kruid, old->uid)) {
retval = set_user(new);
if (retval < 0)
goto error;
if (euid != (uid_t) -1)
new->euid = keuid;
if (suid != (uid_t) -1)
new->suid = ksuid;
new->fsuid = new->euid;

retval = security_task_fix_setuid(new, old, LSM_SETID_RES);
if (retval < 0)
goto error;

return commit_creds(new);

return retval;

The fault must be injected precisely, such that the ns_capable_setid passes. We are unable to precisely control the fault to within an instruction’s execution time (we are – as far as I’m aware – unable to synchronise to the Broadcom SoC’s internal PLL), but fortunately, the instructions highlighted in blue are not required for our attack to succeed, some instruction corruption here is fine, as long as we do not end up with a kernel panic. And there are some wierd, wallpaper-worthy kernel panics:

To set up our test harness, we create a small usermode program, available in the git repo, which uses a memory mapped GPIO to trigger a ChipWhisperer’s delay, then a glitch is inserted in the middle of the sys_setresuid call. Several additional checks are made, to determine if we have “won”, as well as a sanity counter to determine when we our glitches are too far forward: regular corruption of the counting loop indicates that the ext_offset of our glitches is too large.

We must be careful in leveraging our success as well – “/bin/bash” and “/bin/sh” both drop privileges upon execution, so we must use “/bin/dash”, which does not – this cost me a number of successful glitches.

A little while of glitching later, and we have success:

Along the way, a large number of crashes within the kernel were generated. I built a simple Python script to generate some simple statistics. Of note:

  • At a high level, 4982 glitch attempts were made across a few hours in one afternoon (on and off). Of these, 1393 were “crashes” of some description, and 4 attempts resulted in a successful privilege escalation, giving this instance of the attack a success rate of just under 0.1%.
    • Note that the average time to a successful glitch is 15 to 30 minutes, once a narrow range of successful glitching has been established.
  • The top three locations in the kernel for crashes to occur during glitching (giving clues to the severity and type of corruption) are:
    • cap_capable+0x2C with 106 instances
    • commit_creds+0x90 with 94 instances
    • cap_capable+0x14 with 58 instances
  • A good portion of the cap_capable glitches involved pointer derefrencing to near-zero addresses. Given that we are likely seeding the zero pointer, perhaps it is possible to *vastly* increase the likelihood of success by pointing cap_capable to pre-poisoned structures, established in userland (and semi-bypassing KASLR from crash reporting?)
  • Twice, the system suffered “unsafe recovery”, where the system would appear to recover, but executables would fail to run, as if corruption in some fundamental system ABI would occur, unpredictably. For example:

  • A variant of this is that the executable would “mute”, and simply do nothing, needing to be rebuilt before working again.

Thankyou to the Riscure team for their original public discussion of this type of attack – given the potential impacts, I’m surprised this style of attack is not further discussed in the industry. This type of work is eminently enjoyable, and I look forward to investigating similar techniques further.

About Norman

Sometimes, I write code. Occasionally, it even works.
This entry was posted in Bards, Computers, Jesting. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.