SELinux RKP misconfiguration on Samsung S20 devices

by Vitaly Nikolenko

Posted on August 13, 2020 at 8:50PM

By now most of the S20(+ 4G/5G) devices based on 4.19 kernels received the Samsung June security update (depending on your country/region) that included a patch for SELinux bypass (SVE-2019-15998). For example, S20 devices in Australia received the July update just recently skipping the June update entirely. S20 Ultra 5G, on the other hand, received the patch back in March.

I was curious to see for how long this will go unnoticed / unfixed in S20 devices but apparently the issue was reported to Samsung back in early November 2019:

SVE-2019-15998: Misconfiguration for SEAndroid in RKP

Severity: High
Affected versions: P(9.0), Q (10.0) devices
Reported on: November 8, 2019
Disclosure status: Publicly disclosed.
A vulnerability in RKP allows disabling SEAndroid policy.
The patch protects SEAndroid related variables in the RKP.

The patch changed ss_initialized and selinux_enforcing to use RKP. These two global (data segment) variables are generally used to disable SELinux through some kernel primitive (e.g., some arbitrary or partial kernel write). On Samsung devices these variables are marked read-only from EL1 after initialisation (this protection is enforced by RKP/KDP). Directly writing into these variables hence leads to a kernel panic / device reset.

int selinux_enforcing __kdp_ro;

which places it in the protected section (with linker support) marked read-only from kernel context:

#define __kdp_ro __section(.kdp_ro)

On S20 devices however, these variables were not protected by RKP (starting from the very first release) and SELinux can be trivially disabled using some kernel write primitive:

static int selinux_enforcing_boot;
int selinux_enforcing;

static int __init enforcing_setup(char *str)

The June patch now properly marks selinux_enforcing and ss_initialized as __kdp_ro. Furthermore, these two variables are now added to the kdp_init_struct struct:

typedef struct kdp_init_struct {
        u32 credSize;
        u32 sp_size;
        u32 pgd_mm;
        u32 uid_cred;
        u32 euid_cred;
        u32 gid_cred;
        u32 egid_cred;
        u32 bp_pgd_cred;
        u32 bp_task_cred;
        u32 type_cred;
        u32 security_cred;
        u32 usage_cred;
        u32 cred_task;
        u32 mm_task;
        u32 pid_task;
        u32 rp_task;
        u32 comm_task;
        u32 bp_cred_secptr;
        u32 task_threadinfo;
        u64 verifiedbootstate;
        struct {
                u64 selinux_enforcing_va;
                u64 ss_initialized_va;
} kdp_init_t

and the *_va's are pointing to selinux_enforcing and ss_initialized in the read-only section:

diff -r G980FXXU2ATE6/extracted/init/main.c G980FXXU3ATFG/extracted/init/main.c
> extern int selinux_enforcing __kdp_ro;
> extern int ss_initialized __kdp_ro;
> #endif
>       cred.selinux.selinux_enforcing_va  = (u64)&selinux_enforcing;
>       cred.selinux.ss_initialized_va  = (u64)&ss_initialized;
> #else
>       cred.selinux.selinux_enforcing_va  = 0;
>       cred.selinux.ss_initialized_va  = 0;

This fix might seem like a non-critical patch given that most other vendors don't bother protecting selinux_enforced / ss_initialized variables at all. However, weak(er) kernel exploitation primitives now might be useful on S20 devices prior to the patch.