CVE-2023-5521 (Rust, published 2023-10-11T12:15Z)
Description: value: Incorrect Authorization in GitHub repository tiann/kernelsu prior to v0.6.9.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-863 \u2014 Incorrect Authorization
The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
Repository: https://github.com/tiann/kernelsu
Files changed:
kernel/Makefile
kernel/apk_sign.c
kernel/apk_sign.h
kernel/manager.c
userspace/ksud/Cargo.lock
userspace/ksud/Cargo.toml
userspace/ksud/src/apk_sign.rs
userspace/ksud/src/cli.rs
userspace/ksud/src/debug.rs
Commit message: kernel: harden the signature check (#1027)



Vulnerable code:
KSU_EXPECTED_HASH := 0xb0b91415
ccflags-y += -DEXPECTED_HASH=$(KSU_EXPECTED_HASH)
static __always_inline int
check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
	int sign = -1;
	sign = 1;
		ksu_kernel_read_compat(fp, &size8, 0x8, &pos); // sequence length
		if ((id ^ 0xdeadbeefu) == 0xafa439f5u ||
		    (id ^ 0xdeadbeefu) == 0x2efed62f) {
			ksu_kernel_read_compat(fp, &size4, 0x4,
				    &pos); // signer-sequence length
			ksu_kernel_read_compat(fp, &size4, 0x4, &pos); // signer length
			ksu_kernel_read_compat(fp, &size4, 0x4,
				    &pos); // signed data length
			offset += 0x4 * 3;

			ksu_kernel_read_compat(fp, &size4, 0x4,
				    &pos); // digests-sequence length
			pos += size4;
			offset += 0x4 + size4;

			ksu_kernel_read_compat(fp, &size4, 0x4,
				    &pos); // certificates length
			ksu_kernel_read_compat(fp, &size4, 0x4,
				    &pos); // certificate length
			offset += 0x4 * 2;
#if 0
			int hash = 1;
			signed char c;
			for (i = 0; i < size4; ++i) {
				ksu_kernel_read_compat(fp, &c, 0x1, &pos);
				hash = 31 * hash + c;
			}
			offset += size4;
			pr_info("    size: 0x%04x, hash: 0x%08x\n", size4, ((unsigned) hash) ^ 0x14131211u);
#else
			if (size4 == expected_size) {
				int hash = 1;
				signed char c;
				for (i = 0; i < size4; ++i) {
					ksu_kernel_read_compat(fp, &c, 0x1, &pos);
					hash = 31 * hash + c;
				}
				offset += size4;
				if ((((unsigned)hash) ^ 0x14131211u) ==
				    expected_hash) {
					sign = 0;
					break;
				}
			}
			// don't try again.
			break;
#endif
	return sign;
unsigned ksu_expected_hash = EXPECTED_HASH;
	int rv = param_set_uint(val, kp);
	pr_info("ksu_expected_hash set to %x\n", ksu_expected_hash);
	.get = param_get_uint,
int is_manager_apk(char *path)
int is_manager_apk(char *path)
// return 0 if signature match
int is_manager_apk(char *path);
		if (is_manager_apk(cwd) == 0) {
//
// Created by Thom on 2019/3/8.
//

// Credits: https://github.com/brevent/genuine/blob/master/src/main/jni/apk-sign-v2.c
#include <fcntl.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>

#define MAIN

#ifdef MAIN
#include <stdio.h>
#else

#include "common.h"
#include "openat.h"

#endif

static bool isApkSigBlock42(const char *buffer) {
    // APK Sig Block 42
    return *buffer == 'A'
           && *++buffer == 'P'
           && *++buffer == 'K'
           && *++buffer == ' '
           && *++buffer == 'S'
           && *++buffer == 'i'
           && *++buffer == 'g'
           && *++buffer == ' '
           && *++buffer == 'B'
           && *++buffer == 'l'
           && *++buffer == 'o'
           && *++buffer == 'c'
           && *++buffer == 'k'
           && *++buffer == ' '
           && *++buffer == '4'
           && *++buffer == '2';
}

int checkSignature(const char *path) {
    unsigned char buffer[0x11] = {0};
    uint32_t size4;
    uint64_t size8, size_of_block;

#ifdef DEBUG
    LOGI("check signature for %s", path);
#endif

    int sign = -1;
    int fd = (int) openat(AT_FDCWD, path, O_RDONLY);
#ifdef DEBUG_OPENAT
    LOGI("openat %s returns %d", path, fd);
#endif
    if (fd < 0) {
        return sign;
    }

    sign = 1;
    // https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
    for (int i = 0;; ++i) {
        unsigned short n;
        lseek(fd, -i - 2, SEEK_END);
        read(fd, &n, 2);
        if (n == i) {
            lseek(fd, -22, SEEK_CUR);
            read(fd, &size4, 4);
            if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
#ifdef MAIN
                if (i > 0) {
                    printf("warning: comment length is %d\n", i);
                }
#endif
                break;
            }
        }
        if (i == 0xffff) {
#ifdef MAIN
            printf("error: cannot find eocd\n");
#endif
            goto clean;
        }
    }

    lseek(fd, 12, SEEK_CUR);
    // offset
    read(fd, &size4, 0x4);
    lseek(fd, (off_t) (size4 - 0x18), SEEK_SET);

    read(fd, &size8, 0x8);
    read(fd, buffer, 0x10);
    if (!isApkSigBlock42((char *) buffer)) {
        goto clean;
    }

    lseek(fd, (off_t) (size4 - (size8 + 0x8)), SEEK_SET);
    read(fd, &size_of_block, 0x8);
    if (size_of_block != size8) {
        goto clean;
    }

    for (;;) {
        uint32_t id;
        uint32_t offset;
        read(fd, &size8, 0x8); // sequence length
        if (size8 == size_of_block) {
            break;
        }
        read(fd, &id, 0x4); // id
        offset = 4;
#ifdef MAIN
        // printf("id: 0x%08x\n", id);
#endif
        if ((id ^ 0xdeadbeefu) == 0xafa439f5u || (id ^ 0xdeadbeefu) == 0x2efed62f) {
            read(fd, &size4, 0x4); // signer-sequence length
            read(fd, &size4, 0x4); // signer length
            read(fd, &size4, 0x4); // signed data length
            offset += 0x4 * 3;

            read(fd, &size4, 0x4); // digests-sequence length
            lseek(fd, (off_t) (size4), SEEK_CUR);// skip digests
            offset += 0x4 + size4;

            read(fd, &size4, 0x4); // certificates length
            read(fd, &size4, 0x4); // certificate length
            offset += 0x4 * 2;
#ifdef MAIN
            int hash = 1;
            signed char c;
            for (unsigned i = 0; i < size4; ++i) {
                read(fd, &c, 0x1);
                hash = 31 * hash + c;
            }
            offset += size4;
            // printf("    size: 0x%04x, hash: 0x%08x\n", size4, ((unsigned) hash) ^ 0x14131211u);
            printf("0x%04x 0x%08x\n", size4, ((unsigned) hash) ^ 0x14131211u);
#else
#if defined(GENUINE_SIZE) && defined(GENUINE_HASH)
            if (size4 == GENUINE_SIZE) {
                int hash = 1;
                signed char c;
                for (unsigned i = 0; i < size4; ++i) {
                    read(fd, &c, 0x1);
                    hash = 31 * hash + c;
                }
                offset += size4;
                if ((((unsigned) hash) ^ 0x14131211u) == GENUINE_HASH) {
                    sign = 0;
                    break;
                }
            }
#else
            sign = 0;
            break;
#endif
#endif
        }
        lseek(fd, (off_t) (size8 - offset), SEEK_CUR);
    }

clean:
    close(fd);

    return sign;
}

#ifdef MAIN
int main(int argc, char **argv) {
    if (argc > 1) {
        checkSignature(argv[1]);
    }
}
#endif
 "syn",
 "syn",
 "syn",
 "syn",
 "syn",
 "syn",
version = "1.0.50"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
version = "1.0.23"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
 "syn",
 "syn",
 "syn",
 "syn",
 "syn",
 "syn",
pub fn get_apk_signature(apk: &str) -> Result<(u32, u32)> {
        let offset = 4u32;
        if (id ^ 0xdead_beef_u32) == 0xafa4_39f5_u32 || (id ^ 0xdead_beef_u32) == 0x2efe_d62f_u32 {
            f.read_exact(&mut size4)?; // signer-sequence length
            f.read_exact(&mut size4)?; // signer length
            f.read_exact(&mut size4)?; // signed data length
                                       // offset += 0x4 * 3;

            f.read_exact(&mut size4)?; // digests-sequcence length
            let pos = u32::from_le_bytes(size4);
            f.seek(SeekFrom::Current(i64::from(pos)))?;
            // offset += 0x4 + pos;

            f.read_exact(&mut size4)?; // certificates length
            f.read_exact(&mut size4)?; // certificate length
                                       // offset += 0x4 * 2;

            let mut hash = 1i32;
            let mut c = [0u8; 1];

            let j = u32::from_le_bytes(size4);
            for _ in 0..j {
                f.read_exact(&mut c)?;
                hash = hash.wrapping_mul(31).wrapping_add(i32::from(c[0] as i8));
            }

            // offset += j;

            let out_size = j;
            let out_hash = (hash as u32) ^ 0x1413_1211_u32;

            return Ok((out_size, out_hash));
    Err(anyhow::anyhow!("Unknown error"))
                println!("size: {:#x}, hash: {:#x}", sign.0, sign.1);
fn set_kernel_param(size: u32, hash: u32) -> Result<()> {
        "before size: {:#x} hash: {:#x}",
        read_u32(&expeced_hash_path)?
    std::fs::write(&expeced_hash_path, hash.to_string())?;
        "after size: {:#x} hash: {:#x}",
        read_u32(&expeced_hash_path)?

Fixed code:
KSU_EXPECTED_HASH := c371061b19d8c7d7d6133c6a9bafe198fa944e50c1b31c9d8daa8d7f1fc2d2d6
ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\"
#include "linux/err.h"
#include "linux/gfp.h"
#include "linux/kernel.h"
#include "crypto/hash.h"
#include "linux/slab.h"
#include "linux/version.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
#include "crypto/sha2.h"
#else
#include "crypto/sha.h"
#endif

struct sdesc {
	struct shash_desc shash;
	char ctx[];
};

static struct sdesc *init_sdesc(struct crypto_shash *alg)
{
	struct sdesc *sdesc;
	int size;

	size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
	sdesc = kmalloc(size, GFP_KERNEL);
	if (!sdesc)
		return ERR_PTR(-ENOMEM);
	sdesc->shash.tfm = alg;
	return sdesc;
}

static int calc_hash(struct crypto_shash *alg, const unsigned char *data,
		     unsigned int datalen, unsigned char *digest)
{
	struct sdesc *sdesc;
	int ret;

	sdesc = init_sdesc(alg);
	if (IS_ERR(sdesc)) {
		pr_info("can't alloc sdesc\n");
		return PTR_ERR(sdesc);
	}

	ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
	kfree(sdesc);
	return ret;
}

static int ksu_sha256(const unsigned char *data, unsigned int datalen,
		unsigned char *digest)
{
	struct crypto_shash *alg;
	char *hash_alg_name = "sha256";
	int ret;

	alg = crypto_alloc_shash(hash_alg_name, 0, 0);
	if (IS_ERR(alg)) {
		pr_info("can't alloc alg %s\n", hash_alg_name);
		return PTR_ERR(alg);
	}
	ret = calc_hash(alg, data, datalen, digest);
	crypto_free_shash(alg);
	return ret;
}

static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset,
			unsigned expected_size, const char* expected_sha256)
{
	ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length
	ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length
	ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length

	*offset += 0x4 * 3;

	ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length

	*pos += *size4;
	*offset += 0x4 + *size4;

	ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length
	ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length
	*offset += 0x4 * 2;

	if (*size4 == expected_size) {
		*offset += *size4;

		#define CERT_MAX_LENGTH 1024
		char cert[CERT_MAX_LENGTH];
		if (*size4 > CERT_MAX_LENGTH) {
			pr_info("cert length overlimit\n");
			return false;
		}
		ksu_kernel_read_compat(fp, cert, *size4, pos);
		unsigned char digest[SHA256_DIGEST_SIZE];
		if (IS_ERR(ksu_sha256(cert, *size4, digest))) {
			pr_info("sha256 error\n");
			return false;
		}

		char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
		hash_str[SHA256_DIGEST_SIZE * 2] = '\0';

		bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
		pr_info("sha256: %s, expected: %s\n", hash_str, expected_sha256);
		if (strcmp(expected_sha256, hash_str) == 0) {
			return true;
		}
	}
	return false;
}

static __always_inline bool
check_v2_signature(char *path, unsigned expected_size, const char *expected_sha256)
	bool block_valid;

	const int NOT_EXIST = 0;
	const int INVALID = 1;
	const int VALID = 2;
	int v2_signing_status = NOT_EXIST;
	int v3_signing_status = NOT_EXIST;
		ksu_kernel_read_compat(fp, &size8, 0x8,
				       &pos); // sequence length
		if (id == 0x7109871au) {
			block_valid = check_block(fp, &size4, &pos, &offset,
						  expected_size, expected_sha256);
			v2_signing_status = block_valid ? VALID : INVALID;
		} else if (id == 0xf05368c0u) {
			block_valid = check_block(fp, &size4, &pos, &offset,
						  expected_size, expected_sha256);
			v3_signing_status = block_valid ? VALID : INVALID;
	return (v2_signing_status == NOT_EXIST && v3_signing_status == VALID) ||
	       (v2_signing_status == VALID && v3_signing_status == NOT_EXIST) ||
	       (v2_signing_status == VALID && v3_signing_status == VALID);
const char *ksu_expected_hash = EXPECTED_HASH;
	pr_info("set_expected_hash: %s\n", val);
	int rv = param_set_charp(val, kp);
	pr_info("ksu_expected_hash set to %s\n", ksu_expected_hash);
	.get = param_get_charp,
	.free = param_free_charp,
bool is_manager_apk(char *path)
bool is_manager_apk(char *path)
#include "linux/types.h"

bool is_manager_apk(char *path);
		if (is_manager_apk(cwd)) {
[[package]]
name = "addr2line"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
dependencies = [
 "gimli",
]

[[package]]
name = "async-trait"
version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "backtrace"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
dependencies = [
 "addr2line",
 "cc",
 "cfg-if",
 "libc",
 "miniz_oxide",
 "object",
 "rustc-demangle",
]

[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"

 "syn 1.0.107",
 "syn 1.0.107",
 "syn 1.0.107",
 "syn 1.0.107",
[[package]]
name = "gimli"
version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"

 "syn 1.0.107",
 "sha256",
[[package]]
name = "object"
version = "0.30.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385"
dependencies = [
 "memchr",
]

 "syn 1.0.107",
version = "1.0.69"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
version = "1.0.33"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
 "syn 1.0.107",
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"

[[package]]
name = "sha256"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386"
dependencies = [
 "async-trait",
 "bytes",
 "hex",
 "sha2",
 "tokio",
]

 "syn 1.0.107",
[[package]]
name = "syn"
version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

 "syn 1.0.107",
[[package]]
name = "tokio"
version = "1.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
dependencies = [
 "autocfg",
 "backtrace",
 "bytes",
 "pin-project-lite",
]

 "syn 1.0.107",
 "syn 1.0.107",
 "syn 1.0.107",
sha256 = "1.4.0"
pub fn get_apk_signature(apk: &str) -> Result<(u32, String)> {
    let mut v2_signing: Option<(u32, String)> = None;
    let mut v3_signing: Option<(u32, String)> = None;
        let mut offset = 4u32;
        if id == 0x7109_871a_u32 {
            v2_signing = Some(calc_cert_sha256(&mut f, &mut size4, &mut offset)?);
        } else if id == 0xf053_68c0_u32 {
            v3_signing = Some(calc_cert_sha256(&mut f, &mut size4, &mut offset)?);
    match (v2_signing, v3_signing) {
        (None, Some(s)) => Ok(s),
        (Some(s), None) => Ok(s),
        (Some(s1), Some(s2)) => {
            if s1 == s2 {
                Ok(s1)
            } else {
                Err(anyhow::anyhow!(
                    "Inconsisent signature, v2: {}, v3: {}!",
                    s1.1,
                    s2.1
                ))
            }
        }
        _ => Err(anyhow::anyhow!("Unknown signature!")),
    }
}

fn calc_cert_sha256(
    f: &mut std::fs::File,
    size4: &mut [u8; 4],
    offset: &mut u32,
) -> Result<(u32, String)> {
    f.read_exact(size4)?; // signer-sequence length
    f.read_exact(size4)?; // signer length
    f.read_exact(size4)?; // signed data length
    *offset += 0x4 * 3;

    f.read_exact(size4)?; // digests-sequence length
    let pos = u32::from_le_bytes(*size4); // skip digests
    f.seek(SeekFrom::Current(i64::from(pos)))?;
    *offset += 0x4 + pos;

    f.read_exact(size4)?; // certificates length
    f.read_exact(size4)?; // certificate length
    *offset += 0x4 * 2;

    let cert_len = u32::from_le_bytes(*size4);
    let mut cert: Vec<u8> = vec![0; cert_len as usize];
    f.read_exact(&mut cert)?;
    *offset += cert_len;

    Ok((cert_len, sha256::digest(&cert)))
                println!("size: {:#x}, hash: {}", sign.0, sign.1);
fn set_kernel_param(size: u32, hash: String) -> Result<()> {

        "before size: {:#x} hash: {}",
        std::fs::read_to_string(&expeced_hash_path)?
    std::fs::write(&expeced_hash_path, hash)?;
        "after size: {:#x} hash: {}",
        std::fs::read_to_string(&expeced_hash_path)?