From 76ff3adeabfbb7464325eadfa05ca9800c981e46 Mon Sep 17 00:00:00 2001 From: Shawn Webb Date: Thu, 28 Apr 2022 17:09:23 -0400 Subject: [PATCH] HBSD: Support toggling insecure kmod loading with filesystem extended attributes Add hbsdcontrol support for toggling the loading of individual kernel modules that were deemed insecure or untrustworthy. This makes it so that users can still rely on the global `hardening.insecure_kmod` sysctl tunable yet still load certain insecure/untrustworthy modules. An example hbsdcontrol invocation for permitting the load of a kernel module previously marked insecure/untrustworthy: hbsdcontrol pax disable insecure_kmod /boot/kernel/linux_common.ko Signed-off-by: Shawn Webb Issue: #79 MFC-to: 13-STABLE --- .../hardenedbsd/hbsdcontrol/libhbsdcontrol.c | 7 +++ sys/hardenedbsd/hbsd_control_extattr.c | 54 ++++++++++--------- sys/kern/link_elf.c | 37 ++++++++----- sys/kern/link_elf_obj.c | 38 ++++++++----- sys/sys/linker.h | 2 + sys/sys/pax.h | 8 ++- 6 files changed, 95 insertions(+), 51 deletions(-) diff --git a/contrib/hardenedbsd/hbsdcontrol/libhbsdcontrol.c b/contrib/hardenedbsd/hbsdcontrol/libhbsdcontrol.c index 113372a17c87..5d9e96d7b914 100644 --- a/contrib/hardenedbsd/hbsdcontrol/libhbsdcontrol.c +++ b/contrib/hardenedbsd/hbsdcontrol/libhbsdcontrol.c @@ -96,6 +96,13 @@ const struct pax_feature_entry pax_features[] = { [enable] = "hbsd.pax.disallow_map32bit", }, }, + { + .feature = "insecure_kmod", + .extattr = { + [disable] = "hbsd.hardening.permit_kmod", + [enable] = "hbsd.hardening.forbid_kmod", + }, + }, /* Terminating NULL entry, DO NOT REMOVE! */ {NULL, {0, 0}} }; diff --git a/sys/hardenedbsd/hbsd_control_extattr.c b/sys/hardenedbsd/hbsd_control_extattr.c index 0a68a954697c..b8afd50f3fa1 100644 --- a/sys/hardenedbsd/hbsd_control_extattr.c +++ b/sys/hardenedbsd/hbsd_control_extattr.c @@ -41,6 +41,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -54,6 +55,8 @@ FEATURE(hbsd_control_extattr, "HardenedBSD's extattr based control subsystem."); static int pax_control_extattr_status = PAX_FEATURE_SIMPLE_ENABLED; TUNABLE_INT("hardening.control.extattr.status", &pax_control_extattr_status); +static pax_flag_t pax_control_extattr_get_all(struct thread *td, + struct vnode *vp); static bool pax_control_extattr_active(void); struct pax_feature_entry { @@ -74,6 +77,8 @@ const struct pax_feature_entry pax_features[] = { {"hbsd.pax.noshlibrandom", PAX_NOTE_NOSHLIBRANDOM}, {"hbsd.pax.disallow_map32bit", PAX_NOTE_DISALLOWMAP32BIT}, {"hbsd.pax.nodisallow_map32bit", PAX_NOTE_NODISALLOWMAP32BIT}, + {"hbsd.hardening.permit_kmod", PAX_NOTE_PERMITKMOD}, + {"hbsd.hardening.forbid_kmod", PAX_NOTE_FORBIDKMOD}, {NULL, 0} }; @@ -93,6 +98,21 @@ SYSCTL_INT(_hardening_control_extattr, OID_AUTO, status, int pax_control_extattr_parse_flags(struct thread *td, struct image_params *imgp) +{ + imgp->pax.req_extattr_flags = pax_control_extattr_get_all(td, imgp->vp); + + return (0); +} + +pax_flag_t +pax_control_extattr_kmod(struct thread *td, struct vnode *vp) +{ + return (pax_control_extattr_get_all(td, vp)); +} + +static +pax_flag_t +pax_control_extattr_get_all(struct thread *td, struct vnode *vp) { struct uio uio; struct iovec iov; @@ -107,7 +127,6 @@ pax_control_extattr_parse_flags(struct thread *td, struct image_params *imgp) int error; if (!pax_control_extattr_active()) { - imgp->pax.req_extattr_flags = 0; return (0); } @@ -121,7 +140,7 @@ pax_control_extattr_parse_flags(struct thread *td, struct image_params *imgp) feature_present[i] = false; /* Query the size of extended attribute names list. */ - error = VOP_LISTEXTATTR(imgp->vp, EXTATTR_NAMESPACE_SYSTEM, NULL, + error = VOP_LISTEXTATTR(vp, EXTATTR_NAMESPACE_SYSTEM, NULL, &fsea_list_size, NULL, td); /* @@ -131,22 +150,10 @@ pax_control_extattr_parse_flags(struct thread *td, struct image_params *imgp) * - no FS-EA assigned for the file. */ if (error != 0 || fsea_list_size == 0) { - /* Use system defaults. */ - imgp->pax.req_extattr_flags = 0; - - if (error == EOPNOTSUPP) { - /* - * Use system default, without - * returning an error. - */ - return (0); - } - - return (error); + return (0); } if (fsea_list_size > IOSIZE_MAX) { - error = ENOMEM; goto out; } @@ -165,9 +172,10 @@ pax_control_extattr_parse_flags(struct thread *td, struct image_params *imgp) uio.uio_resid = fsea_list_size; /* Query the FS-EA list. */ - error = VOP_LISTEXTATTR(imgp->vp, EXTATTR_NAMESPACE_SYSTEM, &uio, NULL, NULL, td); - if (error != 0) + error = VOP_LISTEXTATTR(vp, EXTATTR_NAMESPACE_SYSTEM, &uio, NULL, NULL, td); + if (error != 0) { goto out; + } /* * Create a filter from existing hbsd.pax attributes. @@ -184,8 +192,9 @@ pax_control_extattr_parse_flags(struct thread *td, struct image_params *imgp) * compare the string's len without the ending zero * with the attribute name stored without zero. */ - if (fsea_attrname_len != entry_size) + if (fsea_attrname_len != entry_size) { continue; + } /* * Compare the strings as byte arrays without the ending zeros @@ -226,7 +235,7 @@ pax_control_extattr_parse_flags(struct thread *td, struct image_params *imgp) * Use NOCRED as credential to always get the extended attributes, * even if the user execs a program. */ - error = VOP_GETEXTATTR(imgp->vp, EXTATTR_NAMESPACE_SYSTEM, + error = VOP_GETEXTATTR(vp, EXTATTR_NAMESPACE_SYSTEM, pax_features[i].fs_ea_attribute, &uio, NULL, NOCRED, td); if (error == 0) { @@ -261,13 +270,8 @@ pax_control_extattr_parse_flags(struct thread *td, struct image_params *imgp) out: free(fsea_list, M_TEMP); - /* In case of error, reset to the system defaults. */ - if (error) - parsed_flags = 0; + return (parsed_flags); - imgp->pax.req_extattr_flags = parsed_flags; - - return (error); } static bool diff --git a/sys/kern/link_elf.c b/sys/kern/link_elf.c index b06db939c5b8..0dcfbf4b1d4a 100644 --- a/sys/kern/link_elf.c +++ b/sys/kern/link_elf.c @@ -168,10 +168,11 @@ static long link_elf_symtab_get(linker_file_t, const Elf_Sym **); static long link_elf_strtab_get(linker_file_t, caddr_t *); static int elf_lookup(linker_file_t, Elf_Size, int, Elf_Addr *); +static bool link_elf_kmod_is_insecure(linker_file_t lf); static int link_elf_detect_insecure_early(struct thread *td, struct vnode *vp, const char *filename); static int link_elf_detect_insecure_late(struct thread *td, - const char *filename, linker_file_t lf); + const char *filename, struct vnode *vp, linker_file_t lf); static kobj_method_t link_elf_methods[] = { KOBJMETHOD(linker_lookup_symbol, link_elf_lookup_symbol), @@ -220,6 +221,15 @@ static struct elf_set_head set_pcpu_list; static struct elf_set_head set_vnet_list; #endif +static bool +link_elf_kmod_is_insecure(linker_file_t lf) +{ + c_linker_sym_t insecure_sym; + + return (link_elf_lookup_symbol(lf, "__insecure_kmod", + &insecure_sym) != ENOENT); +} + static int link_elf_detect_insecure_early(struct thread *td, struct vnode *vp, const char *filename) @@ -230,18 +240,21 @@ link_elf_detect_insecure_early(struct thread *td, struct vnode *vp, static int link_elf_detect_insecure_late(struct thread *td, const char *filename, - linker_file_t lf) + struct vnode *vp, linker_file_t lf) { - c_linker_sym_t insecure_sym; + pax_flag_t flags; - if (link_elf_lookup_symbol(lf, "__insecure_kmod", &insecure_sym) != - ENOENT) { - if (!pax_insecure_kmod()) { - pax_log_internal(td->td_proc, PAX_LOG_P_COMM, - "Insecure kernel module load attempt rejected: %s", - filename != NULL ? filename : ""); - return (EPERM); - } + flags = pax_control_extattr_kmod(td, vp); + if ((flags & PAX_NOTE_PERMITKMOD) == PAX_NOTE_PERMITKMOD) { + return (0); + } + + if ((flags & PAX_NOTE_FORBIDKMOD) == PAX_NOTE_FORBIDKMOD || + (link_elf_kmod_is_insecure(lf) && !pax_insecure_kmod())) { + pax_log_internal(td->td_proc, PAX_LOG_P_COMM, + "Insecure kernel module load attempt rejected: %s", + filename != NULL ? filename : ""); + return (EPERM); } return (0); @@ -1350,7 +1363,7 @@ link_elf_load_file(linker_class_t cls, const char* filename, ef->ddbstrcnt = strcnt; ef->ddbstrtab = ef->strbase; - error = link_elf_detect_insecure_late(td, filename, lf); + error = link_elf_detect_insecure_late(td, filename, nd.ni_vp, lf); if (error != 0) { goto out; } diff --git a/sys/kern/link_elf_obj.c b/sys/kern/link_elf_obj.c index d31e77af48b9..0484acb756ba 100644 --- a/sys/kern/link_elf_obj.c +++ b/sys/kern/link_elf_obj.c @@ -42,6 +42,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -158,10 +159,11 @@ static long link_elf_strtab_get(linker_file_t, caddr_t *); static int elf_obj_lookup(linker_file_t lf, Elf_Size symidx, int deps, Elf_Addr *); +static bool link_elf_kmod_is_insecure(linker_file_t lf); static int link_elf_detect_insecure_early(struct thread *td, struct vnode *vp, const char *filename); static int link_elf_detect_insecure_late(struct thread *td, - const char *filename, linker_file_t lf); + const char *filename, struct vnode *vp, linker_file_t lf); static kobj_method_t link_elf_methods[] = { KOBJMETHOD(linker_lookup_symbol, link_elf_lookup_symbol), @@ -199,6 +201,15 @@ SYSCTL_BOOL(_debug, OID_AUTO, link_elf_obj_leak_locals, static int relocate_file(elf_file_t ef); static void elf_obj_cleanup_globals_cache(elf_file_t); +static bool +link_elf_kmod_is_insecure(linker_file_t lf) +{ + c_linker_sym_t insecure_sym; + + return (link_elf_lookup_symbol(lf, "__insecure_kmod", + &insecure_sym) != ENOENT); +} + static int link_elf_detect_insecure_early(struct thread *td, struct vnode *vp, const char *filename) @@ -209,18 +220,21 @@ link_elf_detect_insecure_early(struct thread *td, struct vnode *vp, static int link_elf_detect_insecure_late(struct thread *td, const char *filename, - linker_file_t lf) + struct vnode *vp, linker_file_t lf) { - c_linker_sym_t insecure_sym; + pax_flag_t flags; - if (link_elf_lookup_symbol(lf, "__insecure_kmod", &insecure_sym) != - ENOENT) { - if (!pax_insecure_kmod()) { - pax_log_internal(td->td_proc, PAX_LOG_P_COMM, - "Insecure kernel module load attempt rejected: %s", - filename != NULL ? filename : ""); - return (EPERM); - } + flags = pax_control_extattr_kmod(td, vp); + if ((flags & PAX_NOTE_PERMITKMOD) == PAX_NOTE_PERMITKMOD) { + return (0); + } + + if ((flags & PAX_NOTE_FORBIDKMOD) == PAX_NOTE_FORBIDKMOD || + (link_elf_kmod_is_insecure(lf) && !pax_insecure_kmod())) { + pax_log_internal(td->td_proc, PAX_LOG_P_COMM, + "Insecure kernel module load attempt rejected: %s", + filename != NULL ? filename : ""); + return (EPERM); } return (0); @@ -1240,7 +1254,7 @@ link_elf_load_file(linker_class_t cls, const char *filename, goto out; } - error = link_elf_detect_insecure_late(td, filename, lf); + error = link_elf_detect_insecure_late(td, filename, nd->ni_vp, lf); if (error != 0) { goto out; } diff --git a/sys/sys/linker.h b/sys/sys/linker.h index 2d65630cd66b..08e0bd4707cc 100644 --- a/sys/sys/linker.h +++ b/sys/sys/linker.h @@ -35,6 +35,7 @@ #include #include +#include #ifdef MALLOC_DECLARE MALLOC_DECLARE(M_LINKER); @@ -104,6 +105,7 @@ struct linker_file { caddr_t exidx_addr; /* Unwind data index table start */ size_t exidx_size; /* Unwind data index table size */ #endif + pax_flag_t pax_flags; }; /* diff --git a/sys/sys/pax.h b/sys/sys/pax.h index da27e5c51d80..cf259fdb500f 100644 --- a/sys/sys/pax.h +++ b/sys/sys/pax.h @@ -125,6 +125,7 @@ bool pax_feature_simple_validate_state(pax_state_t *state); */ int pax_control_acl_set_flags(struct thread *td, struct image_params *imgp, const pax_flag_t req_flags); int pax_control_extattr_parse_flags(struct thread *td, struct image_params *imgp); +pax_flag_t pax_control_extattr_kmod(struct thread *td, struct vnode *vp); /* * ASLR related functions @@ -241,17 +242,20 @@ bool pax_insecure_kmod(void); #define PAX_NOTE_NOSHLIBRANDOM 0x00000200 #define PAX_NOTE_DISALLOWMAP32BIT 0x00000400 #define PAX_NOTE_NODISALLOWMAP32BIT 0x00000800 +#define PAX_NOTE_PERMITKMOD 0x00001000 +#define PAX_NOTE_FORBIDKMOD 0x00002000 #define PAX_NOTE_RESERVED0 0x40000000 #define PAX_NOTE_PREFER_ACL 0x80000000 #define PAX_NOTE_ALL_ENABLED \ (PAX_NOTE_PAGEEXEC | PAX_NOTE_MPROTECT | PAX_NOTE_SEGVGUARD | \ - PAX_NOTE_ASLR | PAX_NOTE_SHLIBRANDOM | PAX_NOTE_DISALLOWMAP32BIT) + PAX_NOTE_ASLR | PAX_NOTE_SHLIBRANDOM | PAX_NOTE_DISALLOWMAP32BIT | \ + PAX_NOTE_PERMITKMOD) #define PAX_NOTE_ALL_DISABLED \ (PAX_NOTE_NOPAGEEXEC | PAX_NOTE_NOMPROTECT | \ PAX_NOTE_NOSEGVGUARD | PAX_NOTE_NOASLR | PAX_NOTE_NOSHLIBRANDOM | \ - PAX_NOTE_NODISALLOWMAP32BIT) + PAX_NOTE_NODISALLOWMAP32BIT | PAX_NOTE_FORBIDKMOD) #define PAX_NOTE_ALL (PAX_NOTE_ALL_ENABLED | PAX_NOTE_ALL_DISABLED | PAX_NOTE_PREFER_ACL) #define PAX_HARDENING_SHLIBRANDOM 0x00000100