Wednesday, February 12, 2020

Mitigations are attack surface, too

Posted by Jann Horn, Project Zero

Introduction
This blog post discusses a bug leading to memory corruption in Samsung's Android kernel (specifically cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Galaxy A50, A505FN - I haven't looked at Samsung's kernels for ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r devices). I will describe cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug and how I wrote a (very unreliable) exploit for it. I will also describe how a second vulnerability, which had long been fixed in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream kernel, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream stable releases, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Android common kernel, but not in Samsung's kernel, aided in its exploitation.

If you want to look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corresponding source code yourself, you can download Samsung's kernel sources for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 A505FN from here. The versions seem to be sorted such that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 newer ones are at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 top of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 list; A505FNXXS3ASK9 is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 newest one at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time of writing, corresponding to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 November 2019 security patch level.

Vendor-specific kernel modifications

On Android, it is normal for vendors to add device-specific code to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel. This code is a frequent source of security vulnerabilities. Android has been reducing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security impact of such code by locking down which processes have access to device drivers, which are often vendor-specific. Modern Android phones access hardware devices through dedicated helper processes, which form cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Hardware Abstraction Layer (HAL).

(As an aside: Linux supports secure, direct hardware access from userspace to PCI devices via Virtual Function I/O (since Linux 3.6), and to USB devices via /dev/bus/usb/. If more OEMs used cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se mechanisms instead of out-of-tree drivers, this would improve cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir device security. It would also help with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue of maintaining those drivers, since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se mechanisms use stable userspace APIs instead of kernel APIs that have no such guarantees.)

Unfortunately, it is more difficult to generically lock down cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attack surface that is created when vendors modify core kernel functionality.

For example, Samsung's kernel adds extra "protection" to credential structures: struct cred is made read-only with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 assistance of hypervisor code (CONFIG_RKP_KDP, "Protection for cred structure"), and transitions to UID 0 are subject to special checks based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current executable (CONFIG_SEC_RESTRICT_SETUID, “Restrict changing root privilege except allowed process”). But none of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se modifications actually prevent an attacker who has sufficient control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel to modify credential structures from reading or modifying user data directly. For example, an attacker could:

  • In order to directly gain access to resources that are supposed to be inaccessible to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker:
    • modify file-system-internal data structures to give cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365mselves access to inodes that wouldn't be accessible normally (as demonstrated furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r down in this blogpost)
    • directly read secrets from kernel memory
  • In order to gain control over processes that have interesting privileges or access to interesting data, such as an email application, a messenger app, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote or system_server - since virtually all user data is accessible to at least one userspace context:
    • modify userspace code that is present in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page cache through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 direct mapping (also known as "physmap" among security folks)
    • modify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 saved register state of ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r userspace processes that are stored in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel
    • modify userspace pointers that are saved in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel and will later be used to write to userspace
    • modify memory management state such that victim-process-owned pages will become accessible to an attacker process

Of course, this is a non-exhaustive list. In ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r words, Samsung's protection mechanisms won't provide meaningful protection against malicious attackers trying to hack your phone, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y only block straightforward rooting tools that haven't been customized for Samsung phones. My opinion is that such modifications are not worth cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 cost because:

  • They make it more difficult to rebase onto a new upstream kernel, which should be happening more often than it currently does
  • They add additional attack surface

Samsung's "Process Aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365nticator" (PROCA)

The subsystem

The Samsung kernel on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 A50 contains an extra security subsystem (named "PROCA", short for "Process Aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365nticator", with code in security/proca/) to track process identities. By combining several logic issues in this subsystem (which, on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir own, can already cause a mismatch between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 tracking state and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 actual process state) with a brittle code pattern, it is possible to cause memory unsafety by winning a race condition.

PROCA seems to track information about process identities based on ASN.1-encoded signatures attached to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir executable files. It might be making that information available to a hypervisor, but that's just speculation on my part. The ASN.1-encoded signatures are loaded from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 extended attribute user.pa ; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 extended attribute security.five also plays a role. I believe that offsets into PROCA's data structures are exposed to something outside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 GAFINFO structure in drivers/staging/samsung/sec_gaf_v5.c - this might be for a hypervisor, but if so, I haven't yet figured out where that hypervisor code is on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 A50 or how that hypervisor can protect accesses to PROCA's data structures against concurrency, given that GAFINFO doesn't contain any information about where locks are located.

There are only a small number of files in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 /system and /vendor filesystems that have a user.pa attribute:

  • /vendor/bin/hw/wpa_supplicant
  • /vendor/bin/vendor.samsung.security.wsm@1.0-service
  • /system/app/SecurityLogAgent/SecurityLogAgent.apk
  • /system/app/Bluetooth/oat/arm64/Bluetooth.odex
  • /system/app/Bluetooth/Bluetooth.apk
  • /system/priv-app/Fast/oat/arm64/Fast.odex
  • /system/priv-app/Fast/Fast.apk
  • /system/bin/apk_signer
  • /system/bin/dex2oat
  • /system/bin/patchoat
  • /system/bin/vold
  • /system/framework/oat/arm64/services.odex
  • /system/framework/services.jar

(The signatures can be decoded on a Linux machine after mounting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystems from a factory image with a command like "getfattr -e base64 -n user.pa system/bin/dex2oat | grep -F 'user.pa=' | sed 's|^user.pa=0s||' | openssl asn1parse"; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ASN.1 structure can be seen in security/proca/proca_certificate.asn1.)

The main data structure of PROCA is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 per-process struct proca_task_descr, which looks as follows (lightly edited output from pahole):

struct proca_task_descr {
  struct task_struct *       task;                /* 0 0x8 */
  struct proca_identity {
    void *             certificate;           /* 0x8 0x8 */
    long unsigned int  certificate_size;           /* 0x10 0x8 */
    struct proca_certificate {
      char *     app_name;                           /* 0x18 0x8 */
      long unsigned int app_name_size;                /* 0x20 0x8 */
      char *     five_signature_hash;                 /* 0x28 0x8 */
      long unsigned int five_signature_hash_size;     /* 0x30 0x8 */
    } parsed_cert;                                  /* 0x18 0x20 */
    struct file *      file;           /* 0x38 0x8 */
  } proca_identity;                               /* 0x8 0x38 */
  struct hlist_node {
    struct hlist_node * next;                       /* 0x40 0x8 */
    struct hlist_node * * pprev;                    /* 0x48 0x8 */
  } pid_map_node;                                 /* 0x40 0x10 */
  struct hlist_node {
    struct hlist_node * next;                       /* 0x50 0x8 */
    struct hlist_node * * pprev;                    /* 0x58 0x8 */
  } app_name_map_node;                            /* 0x50 0x10 */
};

One instance of struct proca_task_descr exists for each running process that is currently executing an executable with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user.pa extended attribute.

Instances of struct proca_task_descr are addressed through g_proca_table, which is a global instance of struct proca_table, a container structure for two hash tables with locks:

struct proca_table {
unsigned int hash_tables_shift;

DECLARE_HASHTABLE(pid_map, PROCA_TASKS_TABLE_SHIFT);
spinlock_t pid_map_lock;

DECLARE_HASHTABLE(app_name_map, PROCA_TASKS_TABLE_SHIFT);
spinlock_t app_name_map_lock;
};

While cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel maintains both hash tables, it only ever performs lookups in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pid_map - cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 app_name_map is eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r unused or used solely for lookups from hypervisor code.

Two logic bugs

pid_map uses numeric PIDs as lookup keys (where PID has cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel meaning "per-task/per-thread ID", not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace meaning "per-thread-group ID"). The following cases are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most interesting ones where this map is modified:

  • When a task creates a child that doesn't share cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 parent's virtual memory mappings (CLONE_VM is unset), five_hook_task_forked() posts a TASK_FORKED work item onto cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 g_hook_workqueue. When this work item is asynchronously processed, if a struct proca_task_descr exists for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 parent's PID, a copy is created for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 child's PID. The kernel's hashtable implementation allows multiple entries with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same key, so if an entry for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 child's PID already exists in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 table, this adds anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r entry.
  • When a task goes through execve() (more specifically, in search_binary_handler()), five_hook_file_processed() collects some information about cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 binary being executed, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n posts a FILE_PROCESSED work item onto cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 g_hook_workqueue. When this work item is asynchronously processed, an item may be inserted into or deleted from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pid_map depending on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pre-execve() state and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new executable.
  • When a struct task_struct is freed, proca_task_free_hook() synchronously removes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 table entry for its PID.

This means that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 state of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PROCA subsystem can easily get out of sync with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 actual state of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system. One problem is that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 point where PROCA believes that an execution has occurred (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security hook in search_binary_handler()) is before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 "point of no return" in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 execve() path: The execution may still abort at a later point, leaving cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 original executable running, but letting PROCA believe that a new executable is now running. If PROCA was actually used to make aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ntication decisions, this might allow an attacker to impersonate a privileged executable while actually running attacker-owned code.

But cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 more interesting logic bug is that PIDs can be reused long before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 proca_task_free_hook() is triggered: On Linux, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PID of a task is available for reallocation once cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task has been reaped (transitioned from Zombie to Dead), but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task is only freed (triggering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 hook) once all refcounted references to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task_struct are gone. While most well-behaved kernel code only takes short-lived references to it, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 binder driver takes long-term references to it - in one place in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream code, in two places in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 version in Android's common kernel tree and Samsung's kernel (because of some priority boosting logic that doesn't exist upstream). This is a potential correctness issue for PROCA, since it means that after a PROCA-aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365nticated task has died, a new process that reuses cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PID might incorrectly be considered aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365nticated; but more importantly, in combination with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following brittle code, it causes a memory safety problem.

A memory safety bug

There is some suspicious locking (dropping and re-acquiring a lock in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 middle of an operation) in proca_table_remove_by_pid() (at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bottom of security/proca/proca_table.c), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 helper used by proca_task_free_hook() to look up and, if successful, remove an entry from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pid_map:

void proca_table_remove_task_descr(struct proca_table *table,
        struct proca_task_descr *descr)
{
[...]
  spin_lock_irqsave(&table->pid_map_lock, irqsave_flags);
  hash_del(&descr->pid_map_node);
  spin_unlock_irqrestore(&table->pid_map_lock, irqsave_flags);

[... same thing for app_name_map ...]
}

struct proca_task_descr *proca_table_get_by_pid(
          struct proca_table *table, pid_t pid)
{
  struct proca_task_descr *descr;
  struct proca_task_descr *target_task_descr = NULL;
  unsigned long hash_key;
  unsigned long irqsave_flags;

  hash_key = calculate_pid_hash(table, pid);

  spin_lock_irqsave(&table->pid_map_lock, irqsave_flags);
  hlist_for_each_entry(descr, &table->pid_map[hash_key], pid_map_node) {
    if (pid == descr->task->pid) {
      target_task_descr = descr;
      break;
    }
  }
  spin_unlock_irqrestore(&table->pid_map_lock, irqsave_flags);

  return target_task_descr;
}

struct proca_task_descr *proca_table_remove_by_pid(
          struct proca_table *table, pid_t pid)
{
  struct proca_task_descr *target_task_descr = NULL;

  target_task_descr = proca_table_get_by_pid(table, pid);
  proca_table_remove_task_descr(table, target_task_descr);

  return target_task_descr;
}

As you can see, proca_table_remove_by_pid() first performs cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 table lookup while holding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pid_map_lock, looking for an item with a matching PID. Then, it grabs a raw pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 item (without incrementing a reference counter or anything like that), drops cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lock, takes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lock again, and removes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 element from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 hash table. This pattern is only safe if it is guaranteed that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re can't be two concurrent calls to proca_table_remove_by_pid() for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same PID. However, this function is called only when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 last reference to a task has been dropped, which, as explained above, can be delayed to a time at which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PID has already been reused. Therefore, this function can be called concurrently for a single PID, and when that happens, this bug can cause a proca_task_descr to be torn down and released a second time. The following operations will happen on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 already-freed proca_task_descr in that case:

  • two hash_del() calls in proca_table_remove_task_descr()
  • destroy_proca_task_descr():
    • deinit_proca_identity():
      • deinit_proca_certificate() calls kfree() on two members
      • fput(identity->file) unless cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file is NULL
      • kfree(identity->certificate)
    • kfree() on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 proca_task_descr

There are several ways one might try to exploit this. I decided to exploit it as a use-after-free list unlink (via hash_del()) and ignore cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 double-free aspect.

Becoming aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365nticated

As a first step, we'll want to make PROCA track one of our attacker-controlled processes. As far as I can tell, this probably isn't supposed to be possible because PROCA only tracks processes that run specific binaries that are marked with special attributes, presumably based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 idea that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y run trusted code; but we can make PROCA track our own processes using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 logic bugs.

One way to do this would be to cause an execution to abort after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security hook in search_binary_handler() (e.g. because something goes wrong while looking up cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interpreter), but I couldn't find an easy way to do this in practice. Therefore, I decided to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second logic bug described in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 section "Two logic bugs", reusing a PID while it is still being tracked:

  • Let cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker-owned process P1 create a child P2 that shares P1's file descriptor table.
  • Let P2 open /dev/binder, causing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resulting file's struct binder_proc to hold a reference to P2.
  • Let P2 execute /system/bin/dex2oat --blah. dex2oat is a file we're allowed to execute from app context (and also from adb shell context) that comes with a user.pa extended attribute, so at this point P2 is tracked by PROCA.
  • dex2oat is called with invalid arguments, so P2 prints some usage information and exits, turning into a Zombie.
  • P1 reaps P2 with waitpid(), so P2 becomes Dead. However, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 /dev/binder file (which is referenced from a file descriptor table shared with P1 and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore wasn't closed when P2 exited) still references P2, so P2's task_struct won't be freed, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 associated entry isn't removed from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pid_map.
  • P1 keeps forking children until cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is a child P3 which has cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same PID as P2. At this point, P3 is tracked by PROCA because its PID has an entry in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pid_map. (If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is hypervisor code involved with PROCA, we don't know how it feels about this, considering that {proca_task_descr}->task still points to P2; but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel code doesn't care.)

Basic race scenario

To cause cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race between two concurrent proca_table_remove_by_pid() calls, we need to have two tasks with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same PID that are freed concurrently. The usual last reference to a task comes through its struct pid, whose lifetime is subject to an unusual flavor of RCU semantics. Since we don't want cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freeing to happen whenever cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel decides to run RCU callbacks, we'll have to create different counted references to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 tasks.

(Irrelevant sidenote: struct pid's refcount does not have RCU semantics, instead cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reference from a living task does. This means that you can, while being in an RCU read-side critical section without any extra locks and without elevating any refcounts, unconditionally increment cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 refcount of any struct pid as long as you got cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer to it from a task_struct in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same RCU read-side critical section. This is a pattern that you don't see much in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Linux kernel.)

One helpful reference-counted pointer to a task exists in binder's struct binder_thread: When a binder_thread is created, it takes a reference to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 calling task, and when it is destroyed via ioctl(..., BINDER_THREAD_EXIT, ...), it synchronously drops a reference on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task. At first I thought that I could only use this for one side of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race: BINDER_THREAD_EXIT only allows us to free binder_thread instances whose PID matches cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 caller's PID, so it seemed like it was impossible to call this twice in parallel for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same PID. For this reason, in my original crasher, I triggered cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 inner side of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race by closing a binder file descriptor, triggering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file's ->release() handler, which posts cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 work item binder_deferred_work onto cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 global workqueue. When this item is processed, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 binder_proc's task reference is dropped.

But actually, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is a way to use BINDER_THREAD_EXIT for both sides of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race: We can create a binder_thread whose ->pid does not match cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PID of its associated task (->task->pid) if we can change cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task's ->pid at a later point. That is possible by calling execve() from a non-leader thread - in ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r words, from a thread ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r than cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one thread cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process had when it last went through execve() or was created by fork(). When this happens, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 calling thread steals cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ->pid of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 leader. (Essentially, on Linux, when a thread that isn't cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main thread calls execve(), that thread first halts all its sibling threads, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n assumes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 identity of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main thread. This is implemented in de_thread() in fs/exec.c.)

So, to trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race, we need to:

  1. Let task A (with PID P1) call BINDER_THREAD_EXIT to start freeing a task with PID P1.
  2. Somehow stall cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 execution of thread A in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 middle of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race window, while cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pid_map_lock is dropped.
  3. Let task B (with PID P2) call BINDER_THREAD_EXIT to free a task with PID P1, using a binder_thread whose ->pid doesn't match ->task->pid. After this step, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 proca_task_descr that task A is currently operating on has been freed.
  4. Reallocate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 proca_task_descr's memory as something else with controlled data.
  5. Let task A continue deleting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 already-freed object (UAF followed by double-free).

Widening cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race window

To create a sufficiently large race window while task A is between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 two locked regions, we can abuse preemption, similar to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mremap() issue I have written about before (see section "Locks and preemption") and gave a talk about (in case you want cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 long video version). However, unlike cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mremap() issue, we are holding a spinlock here until we reach cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spot where we want to preempt cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task. This is very helpful: On CONFIG_PREEMPT systems, if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 scheduler wants to preempt a task while that task is holding a spinlock, it sets a flag on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task that causes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task to move off cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CPU as soon as it's done with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spinlocked region. (Actually, I think that in this case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 flag can't even be set in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 middle of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spinlocked region because interrupts are disabled, too, so I think cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 scheduler IPI is actually only going to be delivered when we reach spin_unlock_irqrestore(). But that doesn't really matter here.)

Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 unlocked region is very short, we'll want to instead make cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spinlocked region before it as big as we can, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n try to preempt task A in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 middle of it. What task A does while holding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lock is a simple hash table lookup:

  hash_key = calculate_pid_hash(table, pid);

  spin_lock_irqsave(&table->pid_map_lock, irqsave_flags);
  hlist_for_each_entry(descr, &table->pid_map[hash_key], pid_map_node) {
    if (pid == descr->task->pid) {
      target_task_descr = descr;
      break;
    }
  }
  spin_unlock_irqrestore(&table->pid_map_lock, irqsave_flags);

Hash tables are supposed to be more or less constant-time under normal conditions, but in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 worst case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y deteriorate into linked lists and have O(n) lookup time instead of ~O(1). (This was presented as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 hashDoS attack at 28C3, with a focus on Denial of Service attacks, but it can also be used for various ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r tricks, including leaking addresses encoded in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lookup key or to widen race windows.) Since calculate_pid_hash() doesn't have any secret parameters, we can easily determine which PIDs will fall into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same list bucket as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one we're using for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race, and fill cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 hash table bucket with lots of proca_task_descr instances whose truncated PID hash collides while cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PID itself doesn't collide, forcing lookups for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target PID to walk through a large number of non-matching entries first.

An unfixed infoleak from September 2018

Figuring out where preemption occurred

At this point, we can repeatedly preempt task A, and one of those preemptions will happen directly at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spin_unlock_irqrestore(). However, we need to figure out which of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 preemption events happens in that spot so that we know when to run cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 inner side of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race. One way to do this would be to just observe scheduling latency, and that should work if we make cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 hash bucket sufficiently big - but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is a nicer way.

Back in September 2018, I reported a security bug in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 /proc/$pid/stack interface and sent a patch that restricts this interface to root. That patch was quickly applied to mainline, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream stable trees, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Android common kernel. Someone even assigned a CVE identifier to it, CVE-2018-17972. But in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Android ecosystem, that doesn't mean that it actually makes its way into device kernels - and at least on this Samsung phone, it still hadn't landed in a build with security patch level "1 November 2019". (The patch is now present in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 newest kernel image, with security updates from February 2020.)

Therefore, we can still use /proc/$pid/stack in our PoC. This file intentionally allows normal users to dump kernel stack traces of any tasks cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y own; it contains a symbolized stack trace, like this (with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 marker 0xffffffffffffffff at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end):

a50:/ $ cat /proc/$$/stack
[<0000000000000000>] __switch_to+0xbc/0xd8
[<0000000000000000>] sigsuspend+0x3c/0x74
[<0000000000000000>] SyS_rt_sigsuspend+0xa0/0xf8
[<0000000000000000>] __sys_trace+0x68/0x68
[<0000000000000000>] 0xffffffffffffffff

This means that every time we have preempted task A, we can read its /proc/$pid/stack and check whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r it contains "proca_table_get_by_pid+"; and when it does, we're in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race window.

(While using /proc/$pid/stack this way makes it easier to write exploits for kernel race conditions, this is not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security bug that caused cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interface to be restricted to root upstream; that security bug will be described in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next section.)

Bypassing PAN using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 direct mapping of a user page

On Linux, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's direct mapping area (also known as "physmap" among security folks) maps almost all memory as RW, including pages that are mapped into userspace as part of normal anonymous memory mappings. If we can locate such a page in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 direct mapping, we have a known kernel address at which we can arbitrarily read/write data directly from userspace.

The security bug in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 /proc/$pid/stack interface was that it performs stack tracing on a task that might be running concurrently, starting with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 saved frame pointer from when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task was last scheduled off a CPU - which means that if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack changed in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 meantime, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's stack tracer can end up interpreting random stack data as stack frames. To make things worse, when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack tracer was unable to symbolize a saved instruction pointer, it would simply print out cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 raw value as a hexadecimal number. You can easily reproduce this as follows on an Android phone affected by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug:

130|a50:/ $ cat /dev/zero > /dev/null &
[1] 8559
a50:/ $ while true; do grep ' 0x' /proc/8559/stack | grep -v 0xffffffffffffffff; done                                                                      
[<0000000000000000>] 0x285ab42995
[<0000000000000000>] 0xffffff8009b63b80
[<0000000000000000>] 0x285ab42995
[<0000000000000000>] 0x285ab42995
[<0000000000000000>] 0x285ab42995
[<0000000000000000>] 0x285ab42995
[<0000000000000000>] 0x285ab42995
[<0000000000000000>] 0x285ab42995
[<0000000000000000>] 0x285ab42995
[<0000000000000000>] 0x285ab42995
[<0000000000000000>] 0xffffffc874325280
[<0000000000000000>] 0xffffffc874325280
[<0000000000000000>] 0xffffffc874325280
[<0000000000000000>] 0xffffffc874325280
[<0000000000000000>] 0x80000000

If we could specifically target things like e.g. cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel-virtual address of a userspace-owned page with this, that would be very helpful for exploit writing.

After a few days spent on figuring out stack frame layouts, I found that this can be done by alternating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following two syscalls:

  1. Call getcwd() on a non-present page and let it block on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mmap semaphore.
    • We need to let anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r thread perform VMA allocations and deallocations in a loop to create contention on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mmap semaphore.
    • The process will be scheduled away when attempting to acquire cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mmap semaphore that has already been taken in write mode. When this happens, cpu_switch_to() in arch/arm64/kernel/entry.S saves cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 callee-saved registers, including cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 frame pointer, into {task}->thread.cpu_context.
    • The frame pointer that was saved when scheduling away ({task}->thread.cpu_context.fp) points to a stack frame whose saved link register is in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same location as where X1 is stored during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r syscall.
  2. Call process_vm_readv() with an output pointer that points to a non-present page.
    • A page fault will be taken in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 middle of copyout(), saving cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 complete register state in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exception frame.
    • The saved X1 in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exception frame contains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel address at which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task's page is mapped by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel. Since process_vm_readv() lets us control cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 offset of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 access, we can use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 low 12 bits of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address as a marker that tells us whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack tracing operation raced in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 right way and leaked cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page pointer.

See addr_leak.c in our bugtracker for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 call stacks and frame sizes.

Similarly, we can leak cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 struct file associated with a pipe using sched_yield() and pipe-to-pipe splice(). The stack layouts for this look as follows (entry-from-EL0 exception frames not shown, since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y look cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same for all syscalls):

In this case, since we can't specify an arbitrary offset here as a signal to distinguish cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target value, I determined cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct file* by performing an ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise identical operation on two different pipe files and filtering out any numbers that show up in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack traces in both cases.

Spraying cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap

The proca_task_descr in which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UAF occurs is allocated using kzalloc(), so it must be in one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kmalloc-* slabs. The slab is selected based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object; a proca_task_descr is 0x60==96 bytes big, which on an X86 desktop system would mean that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation lands in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kmalloc-96 slab. But such a slab doesn't exist on ARM64; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 smallest slab cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is kmalloc-128, which is used for all kmalloc() allocations up to 128 bytes.

Normally, to spray allocations through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 slab allocator, a primitive is used that allocates a memory chunk with a size that falls within cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 right size bucket, fills it with attacker-controlled data, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n keeps a reference to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation around somehow so that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation won't be freed again immediately. However, instead, by relying on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation pattern of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SLUB allocator, we can combine two primitives instead: One to allocate some memory, fill it, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n free it again immediately afterwards, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r to reallocate that memory and keep a reference to it around while leaving most of it uninitialized. We can e.g. do this by alternating calls to recvmsg() with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 payload in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 control message and calls to signalfd(): recvmsg() allocates a temporary buffer for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 control message, copies cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 control message into it and frees cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n signalfd() reallocates cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 128-byte heap chunk as an 8-byte allocation and leaves cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rest of it uninitialized.

Because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UAF will happen on a proca_task_descr that was allocated from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 g_hook_workqueue, whose scheduling we can't control, we don't know on which CPU's slab it was allocated. On top of that, because we need to slowly create dummy proca_task_descr instances to fill up cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 hash bucket after creating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 to-be-UAFed allocation, a significant amount of time passes between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 allocation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 proca_task_descr and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UAF. My PoC attempts to perform reallocations on all CPU cores to avoid missing percpu slabs, but it still doesn't work very reliably, and I didn't want to invest more time into investigating this in detail. (I'm not sure whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r this is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 only major source of unreliability, or whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r parts of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit that are unreliable.)

Getting arbitrary read/write

At this point, we have everything we need for an (unreliable, because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heapspray doesn't work great) proof-of-concept that can gain arbitrary kernel read/write.

We can trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race, reallocating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed buffer using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heapspray. The sprayed fake proca_task_descr can be used to perform a linked list unlink operation on two arbitrary addresses, causing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m to point to each ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r. As one side of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 unlink operation, we can use a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ->private_data member of a pipe file whose address we've leaked; as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r side, we can use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel mapping of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace-owned page whose kernel address we've leaked. With this, we have direct, full control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 struct pipe_inode_info used by this pipe file. By controlling cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe_buffer instances to which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe_inode_info points, we can cause cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe code to read from and write to arbitrary addresses, as long as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y're in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 direct mapping and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 access range doesn't cross page boundaries.

At this point, extending that to full arbitrary read/write is racá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r easy; we can overwrite all sorts of pointers to data that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel will hand back to us.

Doing something useful with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 read/write

Normally we could stop at this point; arbitrary kernel read/write clearly means that all user data reachable by userspace and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel is compromised (unless cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 files are encrypted and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user hasn't entered cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corresponding PIN yet). However, because of Samsung's attempts to prevent exploits from succeeding (such as CONFIG_RKP_KDP), I felt it was necessary to demonstrate that it is possible to access sensitive data using this arbitrary kernel read/write without doing anything particularly complex. Therefore, I wrote some code that can perform path walks through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dentry cache using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 arbitrary read/write (just like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel would do it on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fastpath), look up an inode based on its path in a filesystem, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n install its ->i_mapping as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ->f_mapping of an attacker-owned instance of struct file. The PoC uses this to dump cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 contents of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 accounts database at /data/system_ce/0/accounts_ce.db, which contains sensitive aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ntication tokens. The code for this is fairly straightforward and never touches any credential structures.

Conclusion

Linux kernel code has quite a few sharp edges - and modifications to such a codebase, in particular in a fork without any review from upstream maintainers, can easily introduce subtle issues, even if those modifications are for implementing "security" features.

In my opinion, some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 custom features that Samsung added are unnecessary, and can be removed without any loss of value. I can't tell what PROCA is supposed to do, but e.g., SEC_RESTRICT_SETUID seems to be designed to restrict an attacker who has already gained arbitrary kernel read/write - which to me seems futile, and engineering resources would have been better spent preventing an attacker from getting to that point in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first place.

I believe that device-specific kernel modifications would be better off eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r being upstreamed or moved into userspace drivers, where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y can be implemented in safer programming languages and/or sandboxed, and at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same time won't complicate updates to newer kernel releases.

That I was able to reuse an infoleak bug here that was fixed over a year ago shows, once again, that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way Android device branches are currently maintained is a security problem. While I have criticized some Linux distributions in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 past for not taking patches from upstream in a timely manner, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current situation in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Android ecosystem is worse. Ideally, all vendors should move towards using, and frequently applying updates from, supported upstream kernels.

No comments:

Post a Comment