Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kernel: harden the signature check #1027

Merged
merged 11 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ KSU_EXPECTED_SIZE := 0x033b
endif

ifndef KSU_EXPECTED_HASH
KSU_EXPECTED_HASH := 0xb0b91415
KSU_EXPECTED_HASH := c371061b19d8c7d7d6133c6a9bafe198fa944e50c1b31c9d8daa8d7f1fc2d2d6
endif

$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))

ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
ccflags-y += -DEXPECTED_HASH=$(KSU_EXPECTED_HASH)
ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\"
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
ccflags-y += -Wno-declaration-after-statement
198 changes: 140 additions & 58 deletions kernel/apk_sign.c
Original file line number Diff line number Diff line change
@@ -1,20 +1,136 @@
#include "linux/err.h"
#include "linux/fs.h"
#include "linux/gfp.h"
#include "linux/kernel.h"
#include "linux/moduleparam.h"

#include "apk_sign.h"
#include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h"
#include "crypto/hash.h"
#include "linux/slab.h"
#include "linux/version.h"

static __always_inline int
check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
#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)
{
unsigned char buffer[0x11] = { 0 };
u32 size4;
u64 size8, size_of_block;

loff_t pos;
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;

int sign = -1;
int i;
struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0);
if (IS_ERR(fp)) {
Expand All @@ -25,7 +141,6 @@ check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
// disable inotify for this file
fp->f_mode |= FMODE_NONOTIFY;

sign = 1;
// https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
for (i = 0;; ++i) {
unsigned short n;
Expand Down Expand Up @@ -64,73 +179,38 @@ check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
for (;;) {
uint32_t id;
uint32_t offset;
ksu_kernel_read_compat(fp, &size8, 0x8, &pos); // sequence length
ksu_kernel_read_compat(fp, &size8, 0x8,
&pos); // sequence length
if (size8 == size_of_block) {
break;
}
ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id
offset = 4;
pr_info("id: 0x%08x\n", id);
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
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;
}
pos += (size8 - offset);
}

clean:
filp_close(fp, 0);

return sign;
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);
}

#ifdef CONFIG_KSU_DEBUG

unsigned ksu_expected_size = EXPECTED_SIZE;
unsigned ksu_expected_hash = EXPECTED_HASH;
const char *ksu_expected_hash = EXPECTED_HASH;

#include "manager.h"

Expand All @@ -144,9 +224,10 @@ static int set_expected_size(const char *val, const struct kernel_param *kp)

static int set_expected_hash(const char *val, const struct kernel_param *kp)
{
int rv = param_set_uint(val, kp);
pr_info("set_expected_hash: %s\n", val);
int rv = param_set_charp(val, kp);
ksu_invalidate_manager_uid();
pr_info("ksu_expected_hash set to %x\n", ksu_expected_hash);
pr_info("ksu_expected_hash set to %s\n", ksu_expected_hash);
return rv;
}

Expand All @@ -157,22 +238,23 @@ static struct kernel_param_ops expected_size_ops = {

static struct kernel_param_ops expected_hash_ops = {
.set = set_expected_hash,
.get = param_get_uint,
.get = param_get_charp,
.free = param_free_charp,
};

module_param_cb(ksu_expected_size, &expected_size_ops, &ksu_expected_size,
S_IRUSR | S_IWUSR);
module_param_cb(ksu_expected_hash, &expected_hash_ops, &ksu_expected_hash,
S_IRUSR | S_IWUSR);

int is_manager_apk(char *path)
bool is_manager_apk(char *path)
{
return check_v2_signature(path, ksu_expected_size, ksu_expected_hash);
}

#else

int is_manager_apk(char *path)
bool is_manager_apk(char *path)
{
return check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH);
}
Expand Down
5 changes: 3 additions & 2 deletions kernel/apk_sign.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#ifndef __KSU_H_APK_V2_SIGN
#define __KSU_H_APK_V2_SIGN

// return 0 if signature match
int is_manager_apk(char *path);
#include "linux/types.h"

bool is_manager_apk(char *path);

#endif
2 changes: 1 addition & 1 deletion kernel/manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ bool become_manager(char *pkg)
pr_info("invalid pkg: %s\n", pkg);
continue;
}
if (is_manager_apk(cwd) == 0) {
if (is_manager_apk(cwd)) {
// check passed
uid_t uid = current_uid().val;
pr_info("manager uid: %d\n", uid);
Expand Down
Loading
Loading