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 <shawn.webb@hardenedbsd.org>
Issue:		#79
MFC-to:		13-STABLE
This commit is contained in:
Shawn Webb 2022-04-28 17:09:23 -04:00
parent ea3bba126a
commit 76ff3adeab
6 changed files with 95 additions and 51 deletions

View File

@ -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}}
};

View File

@ -41,6 +41,7 @@ __FBSDID("$FreeBSD$");
#include <sys/ktr.h>
#include <sys/libkern.h>
#include <sys/limits.h>
#include <sys/linker.h>
#include <sys/namei.h>
#include <sys/pax.h>
#include <sys/proc.h>
@ -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

View File

@ -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 : "<unknown>");
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 : "<unknown>");
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;
}

View File

@ -42,6 +42,7 @@ __FBSDID("$FreeBSD$");
#include <sys/mutex.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/pax.h>
#include <sys/proc.h>
#include <sys/rwlock.h>
#include <sys/sysctl.h>
@ -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 : "<unknown>");
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 : "<unknown>");
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;
}

View File

@ -35,6 +35,7 @@
#include <machine/elf.h>
#include <sys/kobj.h>
#include <sys/pax.h>
#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;
};
/*

View File

@ -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