diff --git a/checks/test_secret.c b/checks/test_secret.c new file mode 100644 index 00000000..da6ecc48 --- /dev/null +++ b/checks/test_secret.c @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Host unit tests for credential handling: volatile erasure, constant-time + * comparison, and fd-based secret input (never argv). + * Build: + * gcc -O2 -Wall -Wextra -o /tmp/test_secret checks/test_secret.c && /tmp/test_secret + */ +#include +#include +#include +#include +#include +#include +#include "../kernel/include/kpsecret.h" +#include "../tools/secret_input.h" + +static void test_wipe_and_compare(void) +{ + char buf[64]; + memset(buf, 0x55, sizeof(buf)); + kp_secret_wipe(buf, sizeof(buf)); + for (unsigned int i = 0; i < sizeof(buf); i++) assert(!buf[i]); + + assert(kp_secret_equal("abc", "abc", 3)); + assert(!kp_secret_equal("abc", "abd", 3)); + assert(!kp_secret_equal("abc", "ab", 2) == 0); + + assert(kp_secret_length(NULL, 64) == 64); + assert(kp_secret_length("abc", 2) == 2); + assert(kp_secret_length("abc", 8) == 3); +} + +static void test_fd_input(void) +{ + const char *cases[] = { "valid-key\n", "windows-key\r\n", "", "\n", "too-long" }; + const int expected[] = { 0, 0, -EINVAL, -EINVAL, -E2BIG }; + char buf[64]; + + for (unsigned int i = 0; i < 5; i++) { + int fds[2]; + char fdtext[32]; + unsigned long cap = i == 4 ? 4 : sizeof(buf); + assert(!pipe(fds)); + assert(write(fds[1], cases[i], strlen(cases[i])) == (ssize_t)strlen(cases[i])); + close(fds[1]); + snprintf(fdtext, sizeof(fdtext), "%d", fds[0]); + assert(kp_read_secret_fd(fdtext, buf, cap) == expected[i]); + if (i == 0) assert(!strcmp(buf, "valid-key")); + if (i == 1) assert(!strcmp(buf, "windows-key")); + /* Oversized input must leave the buffer fully wiped, not partial. */ + if (i == 4) for (unsigned long j = 0; j < cap; j++) assert(!buf[j]); + close(fds[0]); + } + + /* Malformed fd arguments are rejected. */ + assert(kp_read_secret_fd("-1", buf, sizeof(buf)) == -EINVAL); + assert(kp_read_secret_fd("4abc", buf, sizeof(buf)) == -EINVAL); + assert(kp_read_secret_fd("", buf, sizeof(buf)) == -EINVAL); +} + +int main(void) +{ + test_wipe_and_compare(); + test_fd_input(); + puts("PASS: credential erasure, constant-time compare, fd secret input"); + return 0; +} diff --git a/kernel/Makefile b/kernel/Makefile index 44e7516e..5bb3e96a 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -119,6 +119,9 @@ base/puff/puff.x86.o: base/puff/puff.c base/sha256.x86.o: base/sha256.c ${X86_CC} $(X86_CFLAGS) $(X86_INCLUDE) -c -O0 -o $@ $< +base/setup.o: ../version +x86/setup.x86.o: ../version + %.o: %.c ${CC} $(CFLAGS) $(INCLUDE) -c -O2 -o $@ $< diff --git a/kernel/base/predata.c b/kernel/base/predata.c index d83393eb..4987a5ac 100644 --- a/kernel/base/predata.c +++ b/kernel/base/predata.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ static bool root_superkey_is_set = false; int auth_superkey(const char *key) { + if (!superkey || !key) return 1; size_t configured_len = lib_strnlen(superkey, SUPER_KEY_LEN); size_t key_len = lib_strnlen(key, SUPER_KEY_LEN + 1); @@ -63,7 +65,9 @@ int auth_superkey(const char *key) sha256_update(&ctx, (const BYTE *)key, key_len); sha256_final(&ctx, hash); int len = SHA256_BLOCK_SIZE > ROOT_SUPER_KEY_HASH_LEN ? ROOT_SUPER_KEY_HASH_LEN : SHA256_BLOCK_SIZE; - rc = lib_memcmp(root_superkey, hash, len); + rc = !kp_secret_equal(root_superkey, hash, len); + kp_secret_wipe(hash, sizeof(hash)); + kp_secret_wipe(&ctx, sizeof(ctx)); static bool first_time = true; if (!rc && first_time) { @@ -78,7 +82,13 @@ int auth_superkey(const char *key) void reset_superkey(const char *key) { - lib_strlcpy(superkey, key, SUPER_KEY_LEN); + size_t len = kp_secret_length(key, SUPER_KEY_LEN); + if (!superkey || !key || !len || len >= SUPER_KEY_LEN) return; + if (key != superkey) { + kp_secret_wipe(superkey, SUPER_KEY_LEN); + lib_memcpy(superkey, key, len); + } + superkey[len] = '\0'; #ifdef CONFIG_X86_64 barrier(); #else diff --git a/kernel/include/kpscauth.h b/kernel/include/kpscauth.h new file mode 100644 index 00000000..e69ce2e8 --- /dev/null +++ b/kernel/include/kpscauth.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _KP_SUPERCALL_AUTH_H_ +#define _KP_SUPERCALL_AUTH_H_ + +/* Include scdefs.h before this file. SU allowlist membership is not administration. */ +static inline int kp_supercall_allowed_for_su(long command) +{ + switch (command) { + case SUPERCALL_HELLO: + case SUPERCALL_KERNELPATCH_VER: + case SUPERCALL_KERNEL_VER: + case SUPERCALL_BUILD_TIME: + case SUPERCALL_SU: + case SUPERCALL_SU_PROFILE: /* dispatch must restrict this to the caller's uid */ + case SUPERCALL_SU_GET_PATH: + case SUPERCALL_SU_GET_SAFEMODE: + return 1; + default: + return 0; + } +} + +#endif diff --git a/kernel/include/kpsecret.h b/kernel/include/kpsecret.h new file mode 100644 index 00000000..b82e5363 --- /dev/null +++ b/kernel/include/kpsecret.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _KP_SECRET_H_ +#define _KP_SECRET_H_ + +/* Volatile stores keep credential erasure observable even under LTO. */ +static inline void kp_secret_wipe(void *memory, unsigned long length) +{ + volatile unsigned char *p = memory; + while (length--) *p++ = 0; +} + +static inline int kp_secret_equal(const void *left, const void *right, unsigned long length) +{ + const unsigned char *a = left, *b = right; + unsigned char difference = 0; + while (length--) difference |= *a++ ^ *b++; + return difference == 0; +} + +static inline unsigned long kp_secret_length(const char *value, unsigned long capacity) +{ + unsigned long length = 0; + if (!value) return capacity; + while (length < capacity && value[length]) length++; + return length; +} + +#endif diff --git a/kernel/patch/common/accctl.c b/kernel/patch/common/accctl.c index 62232e49..fbd078cc 100644 --- a/kernel/patch/common/accctl.c +++ b/kernel/patch/common/accctl.c @@ -65,7 +65,7 @@ int set_all_allow_sctx(const char *sctx) strncpy(all_allow_sctx, sctx, sizeof(all_allow_sctx) - 1); all_allow_sctx[sizeof(all_allow_sctx) - 1] = '\0'; dsb(ish); - logkfd("set all allow sconetxt: %s, sid: %d\n", all_allow_sctx, all_allow_sid); + logkfd("set configured SELinux context: rc=%d\n", rc); } return rc; } @@ -115,8 +115,8 @@ int commit_common_su(uid_t to_uid, const char *sctx) commit_creds(new); out: - logkfi("pid: %d, tgid: %d, to_uid: %d, sctx: %s, via_hook: %d\n", ext ? ext->pid : -1, ext ? ext->tgid : -1, - to_uid, sctx, ext ? ext->sel_allow : 0); + logkfi("pid: %d, tgid: %d, to_uid: %d, rc: %d\n", ext ? ext->pid : -1, ext ? ext->tgid : -1, + to_uid, rc); return rc; } @@ -169,8 +169,7 @@ int task_su(pid_t pid, uid_t to_uid, const char *sctx) } ext->priv_sel_allow = !scontext_changed; - logkfi("pid: %d, tgid: %d, to_uid: %d, sctx: %s, via_hook: %d\n", ext->pid, ext->tgid, to_uid, sctx, - ext->priv_sel_allow); + logkfi("pid: %d, tgid: %d, to_uid: %d, rc: %d\n", ext->pid, ext->tgid, to_uid, rc); out: return rc; } diff --git a/kernel/patch/common/sucompat.c b/kernel/patch/common/sucompat.c index 102e3358..71706fc4 100644 --- a/kernel/patch/common/sucompat.c +++ b/kernel/patch/common/sucompat.c @@ -145,9 +145,11 @@ int su_add_allow_uid(uid_t uid, uid_t to_uid, const char *scontext) uid, to_uid, }; - memcpy(profile.scontext, scontext, SUPERCALL_SCONTEXT_LEN); + size_t sctx_len = strnlen(scontext, sizeof(profile.scontext)); + if (sctx_len >= sizeof(profile.scontext)) return -E2BIG; + memcpy(profile.scontext, scontext, sctx_len + 1); int rc = write_kstorage(su_kstorage_gid, uid, &profile, 0, sizeof(struct su_profile), false); - logkfd("uid: %d, to_uid: %d, sctx: %s, rc: %d\n", uid, to_uid, scontext, rc); + logkfd("uid: %d, to_uid: %d, rc: %d\n", uid, to_uid, rc); return rc; } KP_EXPORT_SYMBOL(su_add_allow_uid); diff --git a/kernel/patch/common/supercall.c b/kernel/patch/common/supercall.c index 0cc7e27a..a2aad427 100644 --- a/kernel/patch/common/supercall.c +++ b/kernel/patch/common/supercall.c @@ -35,6 +35,9 @@ #include #endif +#include +#include + #define MAX_KEY_LEN 128 #include @@ -154,20 +157,33 @@ static long call_su_task(pid_t pid, struct su_profile *__user uprofile) static long call_skey_get(char *__user out_key, int out_len) { + /* Backward compat: return the superkey so callers can verify they hold + * the right credential. If KP_HIDE_SUPERKEY is defined at build time, + * returns -EOPNOTSUPP instead (for hardened deployments where the key + * should never cross the kernel boundary in plaintext). */ +#ifdef KP_HIDE_SUPERKEY + return -EOPNOTSUPP; +#else const char *key = get_superkey(); int klen = strlen(key); - if (klen >= out_len) return -ENOMEM; - int rc = compat_copy_to_user(out_key, key, klen + 1); - return rc; + if (!out_key || out_len <= klen) return -ENOBUFS; + return compat_copy_to_user(out_key, key, klen + 1) == klen + 1 ? 0 : -EFAULT; +#endif } static long call_skey_set(char *__user new_key) { - char buf[SUPER_KEY_LEN]; - int len = compat_strncpy_from_user(buf, new_key, sizeof(buf)); - if (len >= SUPER_KEY_LEN && buf[SUPER_KEY_LEN - 1]) return -E2BIG; - reset_superkey(new_key); - return 0; + char buf[SUPER_KEY_LEN + 2] = { 0 }; + long copied = compat_strncpy_from_user(buf, new_key, sizeof(buf)); + long rc = 0; + unsigned long len = kp_secret_length(buf, sizeof(buf)); + if (copied < 0) rc = copied; + else if (!copied) rc = -EFAULT; + else if (!len) rc = -EINVAL; + else if (len >= SUPER_KEY_LEN) rc = -E2BIG; + else reset_superkey(buf); /* Never dereference a __user pointer in reset_superkey. */ + kp_secret_wipe(buf, sizeof(buf)); + return rc; } static long call_skey_root_enable(int enable) @@ -180,6 +196,10 @@ static long call_grant_uid(struct su_profile *__user uprofile) { struct su_profile *profile = memdup_user(uprofile, sizeof(struct su_profile)); if (!profile || IS_ERR(profile)) return PTR_ERR(profile); + if (strnlen(profile->scontext, sizeof(profile->scontext)) == sizeof(profile->scontext)) { + kvfree(profile); + return -E2BIG; + } int rc = su_add_allow_uid(profile->uid, profile->to_uid, profile->scontext); kvfree(profile); return rc; @@ -274,6 +294,9 @@ static long call_kstorage_remove(int gid, long did) static long supercall(int is_authed, long cmd, long arg1, long arg2, long arg3, long arg4) { + if (!is_authed && !kp_supercall_allowed_for_su(cmd)) return -EPERM; + if (!is_authed && cmd == SUPERCALL_SU_PROFILE && (uid_t)arg1 != current_uid()) return -EPERM; + switch (cmd) { case SUPERCALL_HELLO: logki(SUPERCALL_HELLO_ECHO "\n"); @@ -404,8 +427,9 @@ static void before(hook_fargs6_t *args, void *udata) char key[MAX_KEY_LEN] = { 0 }; long len = compat_strncpy_from_user(key, key_user, MAX_KEY_LEN); + if (len > 0) is_authed = !auth_superkey(key); + kp_secret_wipe(key, sizeof(key)); if (len <= 0) return; - is_authed = !auth_superkey(key); is_trusted_caller = is_authed; } if (is_trusted_manager_uid(uid)) { diff --git a/kernel/patch/common/supercmd.c b/kernel/patch/common/supercmd.c index 46f287c3..5877d5d7 100644 --- a/kernel/patch/common/supercmd.c +++ b/kernel/patch/common/supercmd.c @@ -17,6 +17,7 @@ #include #include #include +#include #ifdef ANDROID #include #endif @@ -131,8 +132,9 @@ static void handle_cmd_sumgr(char **__user u_filename_p, const char **carr, char } if (carr[3]) kstrtoull(carr[3], 10, &to_uid); if (carr[4]) scontext = carr[4]; - su_add_allow_uid(uid, to_uid, scontext); - sprintf(buffer, "grant %d, %d, %s", uid, to_uid, scontext); + cmd_res->rc = su_add_allow_uid(uid, to_uid, scontext); + if (cmd_res->rc) return; + snprintf(buffer, buflen, "grant %llu, %llu", uid, to_uid); cmd_res->msg = buffer; } else if (!strcmp(sub_cmd, "revoke")) { const char *suid = carr[2]; @@ -240,15 +242,22 @@ static void handle_cmd_key_auth(char **__user u_filename_p, const char *cmd, con const char *sub_cmd = carr[1]; if (!sub_cmd) sub_cmd = ""; if (!strcmp("get", sub_cmd)) { +#ifdef KP_HIDE_SUPERKEY + cmd_res->rc = -EOPNOTSUPP; + cmd_res->err_msg = "superkey readback disabled (built with KP_HIDE_SUPERKEY)"; +#else cmd_res->msg = get_superkey(); +#endif } else if (!strcmp("set", sub_cmd)) { const char *key = carr[2]; - if (!key) { + unsigned long len = kp_secret_length(key, SUPER_KEY_LEN); + if (!key || !len || len >= SUPER_KEY_LEN) { + cmd_res->rc = -EINVAL; cmd_res->err_msg = "invalid new key"; return; } - cmd_res->msg = key; reset_superkey(key); + cmd_res->msg = "key updated"; } else if (!strcmp("hash", sub_cmd)) { const char *able = carr[2]; if (able && !strcmp("enable", able)) { @@ -345,11 +354,17 @@ void handle_supercmd(char **__user u_filename_p, char **__user uargv) // long can be distinguished from a valid key ending at the boundary. char arg1[SUPER_KEY_LEN + 2] = { 0 }; long arg1_len = compat_strncpy_from_user(arg1, p1, sizeof(arg1)); - if (arg1_len <= 0 || arg1_len >= sizeof(arg1)) return; + if (arg1_len <= 0 || arg1_len >= sizeof(arg1)) { + kp_secret_wipe(arg1, sizeof(arg1)); + return; + } - if (!auth_superkey(arg1)) { + int valid_key = !auth_superkey(arg1); + int requested_su = !strcmp("su", arg1); + kp_secret_wipe(arg1, sizeof(arg1)); + if (valid_key) { is_key_auth = 1; - } else if (!strcmp("su", arg1)) { + } else if (requested_su) { uid_t uid = current_uid(); if (!is_su_allow_uid(uid) && !is_trusted_manager) return; su_allow_uid_profile(0, uid, &profile); @@ -443,6 +458,13 @@ void handle_supercmd(char **__user u_filename_p, char **__user uargv) goto free; } + if (!is_key_auth && (!strcmp("sumgr", cmd) || !strcmp("reload-cfg", cmd) || + !strcmp("bootlog", cmd) || !strcmp("test", cmd))) { + cmd_res.rc = -EPERM; + cmd_res.err_msg = "administrator authentication required"; + goto echo; + } + if (!strcmp("help", cmd)) { cmd_res.msg = supercmd_help; } else if (!strcmp("-c", cmd)) { @@ -495,7 +517,7 @@ void handle_supercmd(char **__user u_filename_p, char **__user uargv) } echo: - if (cmd_res.msg) supercmd_echo(u_filename_p, uargv, &sp, cmd_res.msg); + if (cmd_res.msg) supercmd_echo(u_filename_p, uargv, &sp, "%s", cmd_res.msg); if (cmd_res.rc) supercmd_echo(u_filename_p, uargv, &sp, "supercmd error code: %d", cmd_res.rc); if (cmd_res.err_msg) supercmd_echo(u_filename_p, uargv, &sp, "supercmd error message: %s", cmd_res.err_msg); @@ -504,6 +526,7 @@ void handle_supercmd(char **__user u_filename_p, char **__user uargv) for (int i = 2; i < sizeof(parr) / sizeof(parr[0]); i++) { const char *a = parr[i]; if (!a) continue; + kp_secret_wipe((void *)a, strlen(a)); kfree(a); } } diff --git a/lkm/supercall/dispatch.c b/lkm/supercall/dispatch.c index 749fe1a7..82b5e94a 100644 --- a/lkm/supercall/dispatch.c +++ b/lkm/supercall/dispatch.c @@ -35,8 +35,8 @@ long kp_control_feature_sc(const char __user *uname, int state) long kp_handle_supercall(long cmd, long a1, long a2, long a3, long a4) { - /* Debug: log every supercall the manager/root app issues. */ - logki("supercall cmd 0x%lx a1=%lx a2=%lx a3=%lx a4=%lx\n", cmd, a1, a2, a3, a4); + /* Argument values may contain userspace pointers or credentials. */ + logkd("supercall cmd 0x%lx\n", cmd); switch (cmd) { case SUPERCALL_HELLO: diff --git a/lkm/supercall/su.c b/lkm/supercall/su.c index aab1258f..79e6257c 100644 --- a/lkm/supercall/su.c +++ b/lkm/supercall/su.c @@ -40,8 +40,11 @@ long kp_su_sc(const struct su_profile __user *uprofile) if (IS_ERR(profile)) return PTR_ERR(profile); - logki("SU requested for to_uid=%u scontext=%.*s\n", profile->to_uid, - (int)sizeof(profile->scontext), profile->scontext); + if (strnlen(profile->scontext, sizeof(profile->scontext)) == sizeof(profile->scontext)) { + kfree(profile); + return -E2BIG; + } + logkd("SU requested for to_uid=%u\n", profile->to_uid); rc = kp_commit_su(profile->to_uid, profile->scontext); kfree(profile); @@ -59,6 +62,10 @@ long kp_su_task_sc(pid_t pid, const struct su_profile __user *uprofile) if (IS_ERR(profile)) return PTR_ERR(profile); + if (strnlen(profile->scontext, sizeof(profile->scontext)) == sizeof(profile->scontext)) { + kfree(profile); + return -E2BIG; + } rc = kp_task_su(pid, profile->to_uid, profile->scontext); kfree(profile); return rc; @@ -75,6 +82,10 @@ long kp_su_grant_uid_sc(const struct su_profile __user *uprofile) if (IS_ERR(profile)) return PTR_ERR(profile); + if (strnlen(profile->scontext, sizeof(profile->scontext)) == sizeof(profile->scontext)) { + kfree(profile); + return -E2BIG; + } rc = kp_su_add_allow_uid(profile->uid, profile->to_uid, profile->scontext); kfree(profile); return rc; @@ -94,7 +105,9 @@ long kp_su_allow_uid_nums_sc(void) long kp_su_allow_uid_list_sc(uid_t __user *uids, int max) { - uid_t list[128]; + if (!uids || max <= 0) + return -EINVAL; + uid_t list[128] = { 0 }; int n = kp_su_allow_uids(list, ARRAY_SIZE(list)); if (n < 0) return n; @@ -102,7 +115,7 @@ long kp_su_allow_uid_list_sc(uid_t __user *uids, int max) n = max; if (copy_to_user(uids, list, n * sizeof(uid_t))) return -EFAULT; - logki("su_allow_uid_list -> %d uids: [%u %u %u %u]\n", n, list[0], list[1], list[2], list[3]); + logkd("su_allow_uid_list -> %d entries\n", n); return n; } diff --git a/lkm/supercall/sucompat.c b/lkm/supercall/sucompat.c index 6cf2aa22..ba9ad4ad 100644 --- a/lkm/supercall/sucompat.c +++ b/lkm/supercall/sucompat.c @@ -55,7 +55,7 @@ int kp_su_add_allow_uid(uid_t uid, uid_t to_uid, const char *scontext) if (scontext) strscpy(profile.scontext, scontext, sizeof(profile.scontext)); int rc = kp_kstorage_write(su_group, (long)uid, &profile, 0, sizeof(profile), false); - logki("allow uid %u -> %u (sctx %s) rc=%d\n", uid, to_uid, profile.scontext, rc); + logkd("allow uid %u -> %u rc=%d\n", uid, to_uid, rc); return rc; } diff --git a/lkm/supercall/supercall.c b/lkm/supercall/supercall.c index fba592b3..dd27d69e 100644 --- a/lkm/supercall/supercall.c +++ b/lkm/supercall/supercall.c @@ -21,6 +21,8 @@ #include "../manager/manager.h" #include "sucompat.h" +#include + #define KP_SUPERCALL_NR 45 /* __NR3264_truncate */ static kp_syscall_fn_t kp_orig_truncate; @@ -31,7 +33,8 @@ static long kp_supercall_handler(const struct pt_regs *regs) /* The manager may grant/revoke allowlist entries; an already-allowed uid * may use the SU supercalls. Anything else is a plain truncate() call. */ - if (!kp_is_manager_uid(uid) && !kp_is_su_allow_uid(uid)) { + bool manager = kp_is_manager_uid(uid); + if (!manager && !kp_is_su_allow_uid(uid)) { logkd("supercall: uid %u not authorized (manager?%d allow?%d)\n", uid, kp_is_manager_uid(uid), kp_is_su_allow_uid(uid)); if (kp_orig_truncate) @@ -46,6 +49,10 @@ static long kp_supercall_handler(const struct pt_regs *regs) return kp_orig_truncate ? kp_orig_truncate(regs) : -EINVAL; } + if (!manager && !kp_supercall_allowed_for_su(cmd)) + return -EPERM; + if (!manager && cmd == SUPERCALL_SU_PROFILE && (uid_t)regs->regs[2] != uid) + return -EPERM; return kp_handle_supercall(cmd, regs->regs[2], regs->regs[3], regs->regs[4], regs->regs[5]); } diff --git a/tools/Makefile b/tools/Makefile index 08c8cc5f..d65bfb74 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -22,6 +22,8 @@ kptools: $(ALL_OBJS) $(CC) -o $@ $^ $(LDFLAGS) +kptools.o: ../version + %.o : %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ diff --git a/tools/kptools.c b/tools/kptools.c index d77bae3b..c7ed47d7 100644 --- a/tools/kptools.c +++ b/tools/kptools.c @@ -25,6 +25,7 @@ #include "common.h" #include "kpm.h" #include "x86_64.h" +#include "secret_input.h" uint32_t version = 0; const char *program_name = NULL; @@ -58,6 +59,9 @@ void print_usage(char **argv) " -k, --kpimg PATH KernelPatch image path.\n" " -s, --skey KEY Set the superkey and save it directly in the boot.img.\n" " -S, --root-skey KEY Set the root-superkey useing hash verification, and the superkey can be changed dynamically.\n" + " --skey-fd FD Read a superkey from FD rather than argv.\n" + " --root-skey-fd FD Read a hash-verification root key from FD.\n" + " --show-secrets Explicitly disclose secrets for image inspection (-l) only.\n" " -o, --out PATH Patched image path.\n" " -a --addition KEY=VALUE Add additional information.\n" @@ -133,6 +137,9 @@ int main(int argc, char *argv[]) { "kpimg", required_argument, NULL, 'k' }, { "skey", required_argument, NULL, 's' }, { "root-skey", required_argument, NULL, 'S' }, + { "show-secrets", no_argument, NULL, 0x100 }, + { "skey-fd", required_argument, NULL, 0x101 }, + { "root-skey-fd", required_argument, NULL, 0x102 }, { "out", required_argument, NULL, 'o' }, { "addition", required_argument, NULL, 'a' }, @@ -150,6 +157,9 @@ int main(int argc, char *argv[]) char *out_path = NULL; char *superkey = NULL; bool root_skey = false; + bool disclose_secrets = false; + const char *key_fd_text = NULL; + char secret_buffer[SUPER_KEY_LEN] = { 0 }; int additional_num = 0; const char *additional[16] = { 0 }; @@ -175,6 +185,20 @@ int main(int argc, char *argv[]) case 'l': cmd = opt; break; + case 0x100: + disclose_secrets = true; + break; + case 0x102: + root_skey = true; + /* fall through */ + case 0x101: + if (key_fd_text) { + fprintf(stderr, "only one key fd may be specified\n"); + free(extra_configs); + return 2; + } + key_fd_text = optarg; + break; case 'i': kimg_path = optarg; break; @@ -221,6 +245,27 @@ int main(int argc, char *argv[]) break; } } + if (key_fd_text) { + if (superkey || (cmd != 'p' && cmd != 'r')) { + fprintf(stderr, "key fd requires -p or -r and cannot be combined with an argv key\n"); + free(extra_configs); + return 2; + } + int key_rc = kp_read_secret_fd(key_fd_text, secret_buffer, sizeof(secret_buffer)); + if (key_rc) { + fprintf(stderr, "unable to read key from fd (error %d)\n", key_rc); + kp_secret_wipe(secret_buffer, sizeof(secret_buffer)); + free(extra_configs); + return 2; + } + superkey = secret_buffer; + } + if (disclose_secrets && cmd != 'l') { + fprintf(stderr, "--show-secrets is valid only with -l\n"); + free(extra_configs); + return 2; + } + patch_set_show_secrets(disclose_secrets); int ret = 0; if (cmd == 'h') { @@ -250,15 +295,16 @@ int main(int argc, char *argv[]) } else if (cmd == 'r') { ret = reset_key(kimg_path, out_path, superkey); } else if (cmd == 'l') { - if (kimg_path) return print_image_patch_info_path(kimg_path); - if (config && config->path) return print_kpm_info_path(config->path); - if (kpimg_path) return print_kp_image_info_path(kpimg_path); + if (kimg_path) ret = print_image_patch_info_path(kimg_path); + else if (config && config->path) ret = print_kpm_info_path(config->path); + else if (kpimg_path) ret = print_kp_image_info_path(kpimg_path); } else { print_usage(argv); } + kp_secret_wipe(secret_buffer, sizeof(secret_buffer)); free(extra_configs); return ret; diff --git a/tools/patch.c b/tools/patch.c index 0e74a7b9..4cc1348d 100644 --- a/tools/patch.c +++ b/tools/patch.c @@ -242,6 +242,13 @@ static char *bytes_to_hexstr(const unsigned char *data, int len) return buf; } +static bool show_secrets; + +void patch_set_show_secrets(bool enabled) +{ + show_secrets = enabled; +} + void print_preset_info(preset_t *preset) { setup_header_t *header = &preset->header; @@ -257,12 +264,13 @@ void print_preset_info(preset_t *preset) fprintf(stdout, "compile_time=%s\n", header->compile_time); fprintf(stdout, "config=%s,%s\n", is_android ? "android" : "linux", is_debug ? "debug" : "release"); fprintf(stdout, "arch=%s\n", is_x86_64 ? "x86_64" : "arm64"); - fprintf(stdout, "superkey=%s\n", setup->superkey); + fprintf(stdout, "superkey=%.*s\n", SUPER_KEY_LEN, + show_secrets ? (const char *)setup->superkey : ""); // todo: remove compat version if (ver_num > 0xa04) { char *hexstr = bytes_to_hexstr(setup->root_superkey, ROOT_SUPER_KEY_HASH_LEN); - fprintf(stdout, "root_superkey=%s\n", hexstr); + fprintf(stdout, "root_superkey=%s\n", show_secrets ? hexstr : ""); free(hexstr); } @@ -835,7 +843,7 @@ int patch_update_img_buf(const char *kimg, int kimg_len, const char *kpimg_path, // superkey if (!root_key) { - tools_logi("superkey: %s\n", superkey); + tools_logi("superkey configured (value omitted)\n"); strncpy((char *)setup->superkey, superkey, SUPER_KEY_LEN - 1); } else if (superkey && superkey[0] != '\0') { int len = SHA256_BLOCK_SIZE > ROOT_SUPER_KEY_HASH_LEN ? ROOT_SUPER_KEY_HASH_LEN : SHA256_BLOCK_SIZE; @@ -845,9 +853,7 @@ int patch_update_img_buf(const char *kimg, int kimg_len, const char *kpimg_path, sha256_update(&ctx, (const BYTE *)superkey, strnlen(superkey, SUPER_KEY_LEN)); sha256_final(&ctx, buf); memcpy(setup->root_superkey, buf, len); - char *hexstr = bytes_to_hexstr(setup->root_superkey, len); - tools_logi("root superkey hash: %s\n", hexstr); - free(hexstr); + tools_logi("root superkey verifier configured (value omitted)\n"); } else { memset(setup->root_superkey, 0, ROOT_SUPER_KEY_HASH_LEN); tools_logi("root_key mode with empty superkey: root_superkey zeroed\n"); @@ -1037,13 +1043,12 @@ int reset_key(const char *kimg_path, const char *out_path, const char *superkey) preset_t *preset = get_preset(kernel_file.kimg, kernel_file.kimg_len); if (!preset) tools_loge_exit("not patched kernel image\n"); - char *origin_key = strdup((char *)preset->setup.superkey); + memset(preset->setup.superkey, 0, sizeof(preset->setup.superkey)); strcpy((char *)preset->setup.superkey, superkey); - tools_logi("reset superkey: %s -> %s\n", origin_key, preset->setup.superkey); + tools_logi("superkey updated (values omitted)\n"); write_kernel_file(&kernel_file, out_path); - free(origin_key); free_kernel_file(&kernel_file); return 0; diff --git a/tools/patch.h b/tools/patch.h index e40db6f0..5c705341 100644 --- a/tools/patch.h +++ b/tools/patch.h @@ -57,6 +57,7 @@ typedef struct bool is_uncompressed_img; } kernel_file_t; +void patch_set_show_secrets(bool enabled); void read_kernel_file(const char *path, kernel_file_t *kernel_file); void new_kernel_file(kernel_file_t *kernel_file, kernel_file_t *old, int32_t kimg_len, bool is_different_endian); void update_kernel_file_img_len(kernel_file_t *kernel_file, int32_t kimg_len, bool is_different_endian); diff --git a/tools/secret_input.h b/tools/secret_input.h new file mode 100644 index 00000000..a660f3e6 --- /dev/null +++ b/tools/secret_input.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _KP_TOOL_SECRET_INPUT_H_ +#define _KP_TOOL_SECRET_INPUT_H_ + +#include +#include +#include +#include "../kernel/include/kpsecret.h" + +/* Read a single key from a caller-supplied pipe/fd; never put the key in argv. */ +static inline int kp_read_secret_fd(const char *fd_text, char *buffer, unsigned long capacity) +{ + char *end; + long fd; + unsigned long used = 0; + if (!fd_text || !fd_text[0] || !buffer || capacity < 2) return -EINVAL; + errno = 0; + fd = strtol(fd_text, &end, 10); + if (errno || *end || fd < 0 || fd > 0x7fffffffL) return -EINVAL; + kp_secret_wipe(buffer, capacity); + for (;;) { + unsigned char byte; + ssize_t count = read((int)fd, &byte, 1); + if (count < 0 && errno == EINTR) continue; + if (count < 0) { int rc = -errno; kp_secret_wipe(buffer, capacity); return rc; } + if (!count || byte == '\n') break; + if (!byte || used >= capacity - 1) { kp_secret_wipe(buffer, capacity); return -E2BIG; } + buffer[used++] = byte; + } + if (used && buffer[used - 1] == '\r') buffer[--used] = '\0'; + if (!used) return -EINVAL; + buffer[used] = '\0'; + return 0; +} + +#endif