Commit 727dbda1 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'hardening-v6.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux

Pull hardening updates from Kees Cook:
 "As has become normal, changes are scattered around the tree (either
  explicitly maintainer Acked or for trivial stuff that went ignored):

   - Carve out the new CONFIG_LIST_HARDENED as a more focused subset of
     CONFIG_DEBUG_LIST (Marco Elver)

   - Fix kallsyms lookup failure under Clang LTO (Yonghong Song)

   - Clarify documentation for CONFIG_UBSAN_TRAP (Jann Horn)

   - Flexible array member conversion not carried in other tree (Gustavo
     A. R. Silva)

   - Various strlcpy() and strncpy() removals not carried in other trees
     (Azeem Shaikh, Justin Stitt)

   - Convert nsproxy.count to refcount_t (Elena Reshetova)

   - Add handful of __counted_by annotations not carried in other trees,
     as well as an LKDTM test

   - Fix build failure with gcc-plugins on GCC 14+

   - Fix selftests to respect SKIP for signal-delivery tests

   - Fix CFI warning for paravirt callback prototype

   - Clarify documentation for seq_show_option_n() usage"

* tag 'hardening-v6.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux: (23 commits)
  LoadPin: Annotate struct dm_verity_loadpin_trusted_root_digest with __counted_by
  kallsyms: Change func signature for cleanup_symbol_name()
  kallsyms: Fix kallsyms_selftest failure
  nsproxy: Convert nsproxy.count to refcount_t
  integrity: Annotate struct ima_rule_opt_list with __counted_by
  lkdtm: Add FAM_BOUNDS test for __counted_by
  Compiler Attributes: counted_by: Adjust name and identifier expansion
  um: refactor deprecated strncpy to memcpy
  um: vector: refactor deprecated strncpy
  alpha: Replace one-element array with flexible-array member
  hardening: Move BUG_ON_DATA_CORRUPTION to hardening options
  list: Introduce CONFIG_LIST_HARDENED
  list_debug: Introduce inline wrappers for debug checks
  compiler_types: Introduce the Clang __preserve_most function attribute
  gcc-plugins: Rename last_stmt() for GCC 14+
  selftests/harness: Actually report SKIP for signal tests
  x86/paravirt: Fix tlb_remove_table function callback prototype warning
  EISA: Replace all non-returning strlcpy with strscpy
  perf: Replace strlcpy with strscpy
  um: Remove strlcpy declaration
  ...
parents b03a4342 5f536ac6
...@@ -97,7 +97,7 @@ struct osf_dirent { ...@@ -97,7 +97,7 @@ struct osf_dirent {
unsigned int d_ino; unsigned int d_ino;
unsigned short d_reclen; unsigned short d_reclen;
unsigned short d_namlen; unsigned short d_namlen;
char d_name[1]; char d_name[];
}; };
struct osf_dirent_callback { struct osf_dirent_callback {
......
...@@ -25,7 +25,7 @@ hyp-obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o hyp-init.o host.o ...@@ -25,7 +25,7 @@ hyp-obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o hyp-init.o host.o
cache.o setup.o mm.o mem_protect.o sys_regs.o pkvm.o stacktrace.o ffa.o cache.o setup.o mm.o mem_protect.o sys_regs.o pkvm.o stacktrace.o ffa.o
hyp-obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \ hyp-obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \
../fpsimd.o ../hyp-entry.o ../exception.o ../pgtable.o ../fpsimd.o ../hyp-entry.o ../exception.o ../pgtable.o
hyp-obj-$(CONFIG_DEBUG_LIST) += list_debug.o hyp-obj-$(CONFIG_LIST_HARDENED) += list_debug.o
hyp-obj-y += $(lib-objs) hyp-obj-y += $(lib-objs)
## ##
......
...@@ -26,8 +26,9 @@ static inline __must_check bool nvhe_check_data_corruption(bool v) ...@@ -26,8 +26,9 @@ static inline __must_check bool nvhe_check_data_corruption(bool v)
/* The predicates checked here are taken from lib/list_debug.c. */ /* The predicates checked here are taken from lib/list_debug.c. */
bool __list_add_valid(struct list_head *new, struct list_head *prev, __list_valid_slowpath
struct list_head *next) bool __list_add_valid_or_report(struct list_head *new, struct list_head *prev,
struct list_head *next)
{ {
if (NVHE_CHECK_DATA_CORRUPTION(next->prev != prev) || if (NVHE_CHECK_DATA_CORRUPTION(next->prev != prev) ||
NVHE_CHECK_DATA_CORRUPTION(prev->next != next) || NVHE_CHECK_DATA_CORRUPTION(prev->next != next) ||
...@@ -37,7 +38,8 @@ bool __list_add_valid(struct list_head *new, struct list_head *prev, ...@@ -37,7 +38,8 @@ bool __list_add_valid(struct list_head *new, struct list_head *prev,
return true; return true;
} }
bool __list_del_entry_valid(struct list_head *entry) __list_valid_slowpath
bool __list_del_entry_valid_or_report(struct list_head *entry)
{ {
struct list_head *prev, *next; struct list_head *prev, *next;
......
...@@ -554,7 +554,7 @@ struct mconsole_output { ...@@ -554,7 +554,7 @@ struct mconsole_output {
static DEFINE_SPINLOCK(client_lock); static DEFINE_SPINLOCK(client_lock);
static LIST_HEAD(clients); static LIST_HEAD(clients);
static char console_buf[MCONSOLE_MAX_DATA]; static char console_buf[MCONSOLE_MAX_DATA] __nonstring;
static void console_write(struct console *console, const char *string, static void console_write(struct console *console, const char *string,
unsigned int len) unsigned int len)
...@@ -567,7 +567,7 @@ static void console_write(struct console *console, const char *string, ...@@ -567,7 +567,7 @@ static void console_write(struct console *console, const char *string,
while (len > 0) { while (len > 0) {
n = min((size_t) len, ARRAY_SIZE(console_buf)); n = min((size_t) len, ARRAY_SIZE(console_buf));
strncpy(console_buf, string, n); memcpy(console_buf, string, n);
string += n; string += n;
len -= n; len -= n;
......
...@@ -141,7 +141,7 @@ static int create_tap_fd(char *iface) ...@@ -141,7 +141,7 @@ static int create_tap_fd(char *iface)
} }
memset(&ifr, 0, sizeof(ifr)); memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
strncpy((char *)&ifr.ifr_name, iface, sizeof(ifr.ifr_name) - 1); strscpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
err = ioctl(fd, TUNSETIFF, (void *) &ifr); err = ioctl(fd, TUNSETIFF, (void *) &ifr);
if (err != 0) { if (err != 0) {
...@@ -171,7 +171,7 @@ static int create_raw_fd(char *iface, int flags, int proto) ...@@ -171,7 +171,7 @@ static int create_raw_fd(char *iface, int flags, int proto)
goto raw_fd_cleanup; goto raw_fd_cleanup;
} }
memset(&ifr, 0, sizeof(ifr)); memset(&ifr, 0, sizeof(ifr));
strncpy((char *)&ifr.ifr_name, iface, sizeof(ifr.ifr_name) - 1); strscpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
if (ioctl(fd, SIOCGIFINDEX, (void *) &ifr) < 0) { if (ioctl(fd, SIOCGIFINDEX, (void *) &ifr) < 0) {
err = -errno; err = -errno;
goto raw_fd_cleanup; goto raw_fd_cleanup;
......
...@@ -50,7 +50,6 @@ static inline int printk(const char *fmt, ...) ...@@ -50,7 +50,6 @@ static inline int printk(const char *fmt, ...)
#endif #endif
extern int in_aton(char *str); extern int in_aton(char *str);
extern size_t strlcpy(char *, const char *, size_t);
extern size_t strlcat(char *, const char *, size_t); extern size_t strlcat(char *, const char *, size_t);
extern size_t strscpy(char *, const char *, size_t); extern size_t strscpy(char *, const char *, size_t);
......
...@@ -40,7 +40,7 @@ static int __init make_uml_dir(void) ...@@ -40,7 +40,7 @@ static int __init make_uml_dir(void)
__func__); __func__);
goto err; goto err;
} }
strlcpy(dir, home, sizeof(dir)); strscpy(dir, home, sizeof(dir));
uml_dir++; uml_dir++;
} }
strlcat(dir, uml_dir, sizeof(dir)); strlcat(dir, uml_dir, sizeof(dir));
...@@ -243,7 +243,7 @@ int __init set_umid(char *name) ...@@ -243,7 +243,7 @@ int __init set_umid(char *name)
if (strlen(name) > UMID_LEN - 1) if (strlen(name) > UMID_LEN - 1)
return -E2BIG; return -E2BIG;
strlcpy(umid, name, sizeof(umid)); strscpy(umid, name, sizeof(umid));
return 0; return 0;
} }
...@@ -262,7 +262,7 @@ static int __init make_umid(void) ...@@ -262,7 +262,7 @@ static int __init make_umid(void)
make_uml_dir(); make_uml_dir();
if (*umid == '\0') { if (*umid == '\0') {
strlcpy(tmp, uml_dir, sizeof(tmp)); strscpy(tmp, uml_dir, sizeof(tmp));
strlcat(tmp, "XXXXXX", sizeof(tmp)); strlcat(tmp, "XXXXXX", sizeof(tmp));
fd = mkstemp(tmp); fd = mkstemp(tmp);
if (fd < 0) { if (fd < 0) {
......
...@@ -79,6 +79,11 @@ void __init native_pv_lock_init(void) ...@@ -79,6 +79,11 @@ void __init native_pv_lock_init(void)
static_branch_disable(&virt_spin_lock_key); static_branch_disable(&virt_spin_lock_key);
} }
static void native_tlb_remove_table(struct mmu_gather *tlb, void *table)
{
tlb_remove_page(tlb, table);
}
unsigned int paravirt_patch(u8 type, void *insn_buff, unsigned long addr, unsigned int paravirt_patch(u8 type, void *insn_buff, unsigned long addr,
unsigned int len) unsigned int len)
{ {
...@@ -295,8 +300,7 @@ struct paravirt_patch_template pv_ops = { ...@@ -295,8 +300,7 @@ struct paravirt_patch_template pv_ops = {
.mmu.flush_tlb_kernel = native_flush_tlb_global, .mmu.flush_tlb_kernel = native_flush_tlb_global,
.mmu.flush_tlb_one_user = native_flush_tlb_one_user, .mmu.flush_tlb_one_user = native_flush_tlb_one_user,
.mmu.flush_tlb_multi = native_flush_tlb_multi, .mmu.flush_tlb_multi = native_flush_tlb_multi,
.mmu.tlb_remove_table = .mmu.tlb_remove_table = native_tlb_remove_table,
(void (*)(struct mmu_gather *, void *))tlb_remove_page,
.mmu.exit_mmap = paravirt_nop, .mmu.exit_mmap = paravirt_nop,
.mmu.notify_page_enc_status_changed = paravirt_nop, .mmu.notify_page_enc_status_changed = paravirt_nop,
......
...@@ -60,7 +60,7 @@ static void __init eisa_name_device(struct eisa_device *edev) ...@@ -60,7 +60,7 @@ static void __init eisa_name_device(struct eisa_device *edev)
int i; int i;
for (i = 0; i < EISA_INFOS; i++) { for (i = 0; i < EISA_INFOS; i++) {
if (!strcmp(edev->id.sig, eisa_table[i].id.sig)) { if (!strcmp(edev->id.sig, eisa_table[i].id.sig)) {
strlcpy(edev->pretty_name, strscpy(edev->pretty_name,
eisa_table[i].name, eisa_table[i].name,
sizeof(edev->pretty_name)); sizeof(edev->pretty_name));
return; return;
......
...@@ -273,8 +273,8 @@ static void lkdtm_HUNG_TASK(void) ...@@ -273,8 +273,8 @@ static void lkdtm_HUNG_TASK(void)
schedule(); schedule();
} }
volatile unsigned int huge = INT_MAX - 2; static volatile unsigned int huge = INT_MAX - 2;
volatile unsigned int ignored; static volatile unsigned int ignored;
static void lkdtm_OVERFLOW_SIGNED(void) static void lkdtm_OVERFLOW_SIGNED(void)
{ {
...@@ -305,7 +305,7 @@ static void lkdtm_OVERFLOW_UNSIGNED(void) ...@@ -305,7 +305,7 @@ static void lkdtm_OVERFLOW_UNSIGNED(void)
ignored = value; ignored = value;
} }
/* Intentionally using old-style flex array definition of 1 byte. */ /* Intentionally using unannotated flex array definition. */
struct array_bounds_flex_array { struct array_bounds_flex_array {
int one; int one;
int two; int two;
...@@ -357,6 +357,46 @@ static void lkdtm_ARRAY_BOUNDS(void) ...@@ -357,6 +357,46 @@ static void lkdtm_ARRAY_BOUNDS(void)
pr_expected_config(CONFIG_UBSAN_BOUNDS); pr_expected_config(CONFIG_UBSAN_BOUNDS);
} }
struct lkdtm_annotated {
unsigned long flags;
int count;
int array[] __counted_by(count);
};
static volatile int fam_count = 4;
static void lkdtm_FAM_BOUNDS(void)
{
struct lkdtm_annotated *inst;
inst = kzalloc(struct_size(inst, array, fam_count + 1), GFP_KERNEL);
if (!inst) {
pr_err("FAIL: could not allocate test struct!\n");
return;
}
inst->count = fam_count;
pr_info("Array access within bounds ...\n");
inst->array[1] = fam_count;
ignored = inst->array[1];
pr_info("Array access beyond bounds ...\n");
inst->array[fam_count] = fam_count;
ignored = inst->array[fam_count];
kfree(inst);
pr_err("FAIL: survived access of invalid flexible array member index!\n");
if (!__has_attribute(__counted_by__))
pr_warn("This is expected since this %s was built a compiler supporting __counted_by\n",
lkdtm_kernel_info);
else if (IS_ENABLED(CONFIG_UBSAN_BOUNDS))
pr_expected_config(CONFIG_UBSAN_TRAP);
else
pr_expected_config(CONFIG_UBSAN_BOUNDS);
}
static void lkdtm_CORRUPT_LIST_ADD(void) static void lkdtm_CORRUPT_LIST_ADD(void)
{ {
/* /*
...@@ -393,7 +433,7 @@ static void lkdtm_CORRUPT_LIST_ADD(void) ...@@ -393,7 +433,7 @@ static void lkdtm_CORRUPT_LIST_ADD(void)
pr_err("Overwrite did not happen, but no BUG?!\n"); pr_err("Overwrite did not happen, but no BUG?!\n");
else { else {
pr_err("list_add() corruption not detected!\n"); pr_err("list_add() corruption not detected!\n");
pr_expected_config(CONFIG_DEBUG_LIST); pr_expected_config(CONFIG_LIST_HARDENED);
} }
} }
...@@ -420,7 +460,7 @@ static void lkdtm_CORRUPT_LIST_DEL(void) ...@@ -420,7 +460,7 @@ static void lkdtm_CORRUPT_LIST_DEL(void)
pr_err("Overwrite did not happen, but no BUG?!\n"); pr_err("Overwrite did not happen, but no BUG?!\n");
else { else {
pr_err("list_del() corruption not detected!\n"); pr_err("list_del() corruption not detected!\n");
pr_expected_config(CONFIG_DEBUG_LIST); pr_expected_config(CONFIG_LIST_HARDENED);
} }
} }
...@@ -616,6 +656,7 @@ static struct crashtype crashtypes[] = { ...@@ -616,6 +656,7 @@ static struct crashtype crashtypes[] = {
CRASHTYPE(OVERFLOW_SIGNED), CRASHTYPE(OVERFLOW_SIGNED),
CRASHTYPE(OVERFLOW_UNSIGNED), CRASHTYPE(OVERFLOW_UNSIGNED),
CRASHTYPE(ARRAY_BOUNDS), CRASHTYPE(ARRAY_BOUNDS),
CRASHTYPE(FAM_BOUNDS),
CRASHTYPE(CORRUPT_LIST_ADD), CRASHTYPE(CORRUPT_LIST_ADD),
CRASHTYPE(CORRUPT_LIST_DEL), CRASHTYPE(CORRUPT_LIST_DEL),
CRASHTYPE(STACK_GUARD_PAGE_LEADING), CRASHTYPE(STACK_GUARD_PAGE_LEADING),
......
...@@ -524,7 +524,7 @@ int qe_upload_firmware(const struct qe_firmware *firmware) ...@@ -524,7 +524,7 @@ int qe_upload_firmware(const struct qe_firmware *firmware)
* saved microcode information and put in the new. * saved microcode information and put in the new.
*/ */
memset(&qe_firmware_info, 0, sizeof(qe_firmware_info)); memset(&qe_firmware_info, 0, sizeof(qe_firmware_info));
strlcpy(qe_firmware_info.id, firmware->id, sizeof(qe_firmware_info.id)); strscpy(qe_firmware_info.id, firmware->id, sizeof(qe_firmware_info.id));
qe_firmware_info.extended_modes = be64_to_cpu(firmware->extended_modes); qe_firmware_info.extended_modes = be64_to_cpu(firmware->extended_modes);
memcpy(qe_firmware_info.vtraps, firmware->vtraps, memcpy(qe_firmware_info.vtraps, firmware->vtraps,
sizeof(firmware->vtraps)); sizeof(firmware->vtraps));
...@@ -599,7 +599,7 @@ struct qe_firmware_info *qe_get_firmware_info(void) ...@@ -599,7 +599,7 @@ struct qe_firmware_info *qe_get_firmware_info(void)
/* Copy the data into qe_firmware_info*/ /* Copy the data into qe_firmware_info*/
sprop = of_get_property(fw, "id", NULL); sprop = of_get_property(fw, "id", NULL);
if (sprop) if (sprop)
strlcpy(qe_firmware_info.id, sprop, strscpy(qe_firmware_info.id, sprop,
sizeof(qe_firmware_info.id)); sizeof(qe_firmware_info.id));
of_property_read_u64(fw, "extended-modes", of_property_read_u64(fw, "extended-modes",
......
...@@ -94,6 +94,19 @@ ...@@ -94,6 +94,19 @@
# define __copy(symbol) # define __copy(symbol)
#endif #endif
/*
* Optional: only supported since gcc >= 14
* Optional: only supported since clang >= 18
*
* gcc: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108896
* clang: https://reviews.llvm.org/D148381
*/
#if __has_attribute(__counted_by__)
# define __counted_by(member) __attribute__((__counted_by__(member)))
#else
# define __counted_by(member)
#endif
/* /*
* Optional: not supported by gcc * Optional: not supported by gcc
* Optional: only supported since clang >= 14.0 * Optional: only supported since clang >= 14.0
...@@ -129,19 +142,6 @@ ...@@ -129,19 +142,6 @@
# define __designated_init # define __designated_init
#endif #endif
/*
* Optional: only supported since gcc >= 14
* Optional: only supported since clang >= 17
*
* gcc: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108896
* clang: https://reviews.llvm.org/D148381
*/
#if __has_attribute(__element_count__)
# define __counted_by(member) __attribute__((__element_count__(#member)))
#else
# define __counted_by(member)
#endif
/* /*
* Optional: only supported since clang >= 14.0 * Optional: only supported since clang >= 14.0
* *
......
...@@ -106,6 +106,34 @@ static inline void __chk_io_ptr(const volatile void __iomem *ptr) { } ...@@ -106,6 +106,34 @@ static inline void __chk_io_ptr(const volatile void __iomem *ptr) { }
#define __cold #define __cold
#endif #endif
/*
* On x86-64 and arm64 targets, __preserve_most changes the calling convention
* of a function to make the code in the caller as unintrusive as possible. This
* convention behaves identically to the C calling convention on how arguments
* and return values are passed, but uses a different set of caller- and callee-
* saved registers.
*
* The purpose is to alleviates the burden of saving and recovering a large
* register set before and after the call in the caller. This is beneficial for
* rarely taken slow paths, such as error-reporting functions that may be called
* from hot paths.
*
* Note: This may conflict with instrumentation inserted on function entry which
* does not use __preserve_most or equivalent convention (if in assembly). Since
* function tracing assumes the normal C calling convention, where the attribute
* is supported, __preserve_most implies notrace. It is recommended to restrict
* use of the attribute to functions that should or already disable tracing.
*
* Optional: not supported by gcc.
*
* clang: https://clang.llvm.org/docs/AttributeReference.html#preserve-most
*/
#if __has_attribute(__preserve_most__) && (defined(CONFIG_X86_64) || defined(CONFIG_ARM64))
# define __preserve_most notrace __attribute__((__preserve_most__))
#else
# define __preserve_most
#endif
/* Builtins */ /* Builtins */
/* /*
......
...@@ -12,7 +12,7 @@ extern struct list_head dm_verity_loadpin_trusted_root_digests; ...@@ -12,7 +12,7 @@ extern struct list_head dm_verity_loadpin_trusted_root_digests;
struct dm_verity_loadpin_trusted_root_digest { struct dm_verity_loadpin_trusted_root_digest {
struct list_head node; struct list_head node;
unsigned int len; unsigned int len;
u8 data[]; u8 data[] __counted_by(len);
}; };
#if IS_ENABLED(CONFIG_SECURITY_LOADPIN_VERITY) #if IS_ENABLED(CONFIG_SECURITY_LOADPIN_VERITY)
......
...@@ -38,11 +38,92 @@ static inline void INIT_LIST_HEAD(struct list_head *list) ...@@ -38,11 +38,92 @@ static inline void INIT_LIST_HEAD(struct list_head *list)
WRITE_ONCE(list->prev, list); WRITE_ONCE(list->prev, list);
} }
#ifdef CONFIG_LIST_HARDENED
#ifdef CONFIG_DEBUG_LIST #ifdef CONFIG_DEBUG_LIST
extern bool __list_add_valid(struct list_head *new, # define __list_valid_slowpath
struct list_head *prev, #else
struct list_head *next); # define __list_valid_slowpath __cold __preserve_most
extern bool __list_del_entry_valid(struct list_head *entry); #endif
/*
* Performs the full set of list corruption checks before __list_add().
* On list corruption reports a warning, and returns false.
*/
extern bool __list_valid_slowpath __list_add_valid_or_report(struct list_head *new,
struct list_head *prev,
struct list_head *next);
/*
* Performs list corruption checks before __list_add(). Returns false if a
* corruption is detected, true otherwise.
*
* With CONFIG_LIST_HARDENED only, performs minimal list integrity checking
* inline to catch non-faulting corruptions, and only if a corruption is
* detected calls the reporting function __list_add_valid_or_report().
*/
static __always_inline bool __list_add_valid(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
bool ret = true;
if (!IS_ENABLED(CONFIG_DEBUG_LIST)) {
/*
* With the hardening version, elide checking if next and prev
* are NULL, since the immediate dereference of them below would
* result in a fault if NULL.
*
* With the reduced set of checks, we can afford to inline the
* checks, which also gives the compiler a chance to elide some
* of them completely if they can be proven at compile-time. If
* one of the pre-conditions does not hold, the slow-path will
* show a report which pre-condition failed.
*/
if (likely(next->prev == prev && prev->next == next && new != prev && new != next))
return true;
ret = false;
}
ret &= __list_add_valid_or_report(new, prev, next);
return ret;
}
/*
* Performs the full set of list corruption checks before __list_del_entry().
* On list corruption reports a warning, and returns false.
*/
extern bool __list_valid_slowpath __list_del_entry_valid_or_report(struct list_head *entry);
/*
* Performs list corruption checks before __list_del_entry(). Returns false if a
* corruption is detected, true otherwise.
*
* With CONFIG_LIST_HARDENED only, performs minimal list integrity checking
* inline to catch non-faulting corruptions, and only if a corruption is
* detected calls the reporting function __list_del_entry_valid_or_report().
*/
static __always_inline bool __list_del_entry_valid(struct list_head *entry)
{
bool ret = true;
if (!IS_ENABLED(CONFIG_DEBUG_LIST)) {
struct list_head *prev = entry->prev;
struct list_head *next = entry->next;
/*
* With the hardening version, elide checking if next and prev
* are NULL, LIST_POISON1 or LIST_POISON2, since the immediate
* dereference of them below would result in a fault.
*/
if (likely(prev->next == entry && next->prev == entry))
return true;
ret = false;
}
ret &= __list_del_entry_valid_or_report(entry);
return ret;
}
#else #else
static inline bool __list_add_valid(struct list_head *new, static inline bool __list_add_valid(struct list_head *new,
struct list_head *prev, struct list_head *prev,
......
...@@ -29,7 +29,7 @@ struct fs_struct; ...@@ -29,7 +29,7 @@ struct fs_struct;
* nsproxy is copied. * nsproxy is copied.
*/ */
struct nsproxy { struct nsproxy {
atomic_t count; refcount_t count;
struct uts_namespace *uts_ns; struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns; struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns; struct mnt_namespace *mnt_ns;
...@@ -102,14 +102,13 @@ int __init nsproxy_cache_init(void); ...@@ -102,14 +102,13 @@ int __init nsproxy_cache_init(void);
static inline void put_nsproxy(struct nsproxy *ns) static inline void put_nsproxy(struct nsproxy *ns)
{ {
if (atomic_dec_and_test(&ns->count)) { if (refcount_dec_and_test(&ns->count))
free_nsproxy(ns); free_nsproxy(ns);
}
} }
static inline void get_nsproxy(struct nsproxy *ns) static inline void get_nsproxy(struct nsproxy *ns)
{ {
atomic_inc(&ns->count); refcount_inc(&ns->count);
} }
#endif #endif
...@@ -249,18 +249,19 @@ static inline void seq_show_option(struct seq_file *m, const char *name, ...@@ -249,18 +249,19 @@ static inline void seq_show_option(struct seq_file *m, const char *name,
/** /**
* seq_show_option_n - display mount options with appropriate escapes * seq_show_option_n - display mount options with appropriate escapes
* where @value must be a specific length. * where @value must be a specific length (i.e.
* not NUL-terminated).
* @m: the seq_file handle * @m: the seq_file handle
* @name: the mount option name * @name: the mount option name
* @value: the mount option name's value, cannot be NULL * @value: the mount option name's value, cannot be NULL
* @length: the length of @value to display * @length: the exact length of @value to display, must be constant expression
* *
* This is a macro since this uses "length" to define the size of the * This is a macro since this uses "length" to define the size of the
* stack buffer. * stack buffer.
*/ */
#define seq_show_option_n(m, name, value, length) { \ #define seq_show_option_n(m, name, value, length) { \
char val_buf[length + 1]; \ char val_buf[length + 1]; \
strncpy(val_buf, value, length); \ memcpy(val_buf, value, length); \
val_buf[length] = '\0'; \ val_buf[length] = '\0'; \
seq_show_option(m, name, val_buf); \ seq_show_option(m, name, val_buf); \
} }
......
...@@ -45,3 +45,7 @@ ...@@ -45,3 +45,7 @@
TYPE NAME[]; \ TYPE NAME[]; \
} }
#endif #endif
#ifndef __counted_by
#define __counted_by(m)
#endif
...@@ -8249,7 +8249,7 @@ static void perf_event_comm_event(struct perf_comm_event *comm_event) ...@@ -8249,7 +8249,7 @@ static void perf_event_comm_event(struct perf_comm_event *comm_event)
unsigned int size; unsigned int size;
memset(comm, 0, sizeof(comm)); memset(comm, 0, sizeof(comm));
strlcpy(comm, comm_event->task->comm, sizeof(comm)); strscpy(comm, comm_event->task->comm, sizeof(comm));
size = ALIGN(strlen(comm)+1, sizeof(u64)); size = ALIGN(strlen(comm)+1, sizeof(u64));
comm_event->comm = comm; comm_event->comm = comm;
...@@ -8704,7 +8704,7 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event) ...@@ -8704,7 +8704,7 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event)
} }
cpy_name: cpy_name:
strlcpy(tmp, name, sizeof(tmp)); strscpy(tmp, name, sizeof(tmp));
name = tmp; name = tmp;
got_name: got_name:
/* /*
...@@ -9128,7 +9128,7 @@ void perf_event_ksymbol(u16 ksym_type, u64 addr, u32 len, bool unregister, ...@@ -9128,7 +9128,7 @@ void perf_event_ksymbol(u16 ksym_type, u64 addr, u32 len, bool unregister,
ksym_type == PERF_RECORD_KSYMBOL_TYPE_UNKNOWN) ksym_type == PERF_RECORD_KSYMBOL_TYPE_UNKNOWN)
goto err; goto err;
strlcpy(name, sym, KSYM_NAME_LEN); strscpy(name, sym, KSYM_NAME_LEN);
name_len = strlen(name) + 1; name_len = strlen(name) + 1;
while (!IS_ALIGNED(name_len, sizeof(u64))) while (!IS_ALIGNED(name_len, sizeof(u64)))
name[name_len++] = '\0'; name[name_len++] = '\0';
......
...@@ -163,12 +163,12 @@ unsigned long kallsyms_sym_address(int idx) ...@@ -163,12 +163,12 @@ unsigned long kallsyms_sym_address(int idx)
return kallsyms_relative_base - 1 - kallsyms_offsets[idx]; return kallsyms_relative_base - 1 - kallsyms_offsets[idx];
} }
static bool cleanup_symbol_name(char *s) static void cleanup_symbol_name(char *s)
{ {
char *res; char *res;
if (!IS_ENABLED(CONFIG_LTO_CLANG)) if (!IS_ENABLED(CONFIG_LTO_CLANG))
return false; return;
/* /*
* LLVM appends various suffixes for local functions and variables that * LLVM appends various suffixes for local functions and variables that
...@@ -178,26 +178,21 @@ static bool cleanup_symbol_name(char *s) ...@@ -178,26 +178,21 @@ static bool cleanup_symbol_name(char *s)
* - foo.llvm.[0-9a-f]+ * - foo.llvm.[0-9a-f]+
*/ */
res = strstr(s, ".llvm."); res = strstr(s, ".llvm.");
if (res) { if (res)
*res = '\0'; *res = '\0';
return true;
}
return false; return;
} }
static int compare_symbol_name(const char *name, char *namebuf) static int compare_symbol_name(const char *name, char *namebuf)
{ {
int ret; /* The kallsyms_seqs_of_names is sorted based on names after
* cleanup_symbol_name() (see scripts/kallsyms.c) if clang lto is enabled.
ret = strcmp(name, namebuf); * To ensure correct bisection in kallsyms_lookup_names(), do
if (!ret) * cleanup_symbol_name(namebuf) before comparing name and namebuf.
return ret; */
cleanup_symbol_name(namebuf);
if (cleanup_symbol_name(namebuf) && !strcmp(name, namebuf)) return strcmp(name, namebuf);
return 0;
return ret;
} }
static unsigned int get_symbol_seq(int index) static unsigned int get_symbol_seq(int index)
......
...@@ -196,7 +196,7 @@ static bool match_cleanup_name(const char *s, const char *name) ...@@ -196,7 +196,7 @@ static bool match_cleanup_name(const char *s, const char *name)
if (!IS_ENABLED(CONFIG_LTO_CLANG)) if (!IS_ENABLED(CONFIG_LTO_CLANG))
return false; return false;
p = strchr(s, '.'); p = strstr(s, ".llvm.");
if (!p) if (!p)
return false; return false;
...@@ -344,27 +344,6 @@ static int test_kallsyms_basic_function(void) ...@@ -344,27 +344,6 @@ static int test_kallsyms_basic_function(void)
goto failed; goto failed;
} }
/*
* The first '.' may be the initial letter, in which case the
* entire symbol name will be truncated to an empty string in
* cleanup_symbol_name(). Do not test these symbols.
*
* For example:
* cat /proc/kallsyms | awk '{print $3}' | grep -E "^\." | head
* .E_read_words
* .E_leading_bytes
* .E_trailing_bytes
* .E_write_words
* .E_copy
* .str.292.llvm.12122243386960820698
* .str.24.llvm.12122243386960820698
* .str.29.llvm.12122243386960820698
* .str.75.llvm.12122243386960820698
* .str.99.llvm.12122243386960820698
*/
if (IS_ENABLED(CONFIG_LTO_CLANG) && !namebuf[0])
continue;
lookup_addr = kallsyms_lookup_name(namebuf); lookup_addr = kallsyms_lookup_name(namebuf);
memset(stat, 0, sizeof(*stat)); memset(stat, 0, sizeof(*stat));
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
static struct kmem_cache *nsproxy_cachep; static struct kmem_cache *nsproxy_cachep;
struct nsproxy init_nsproxy = { struct nsproxy init_nsproxy = {
.count = ATOMIC_INIT(1), .count = REFCOUNT_INIT(1),
.uts_ns = &init_uts_ns, .uts_ns = &init_uts_ns,
#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC) #if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
.ipc_ns = &init_ipc_ns, .ipc_ns = &init_ipc_ns,
...@@ -55,7 +55,7 @@ static inline struct nsproxy *create_nsproxy(void) ...@@ -55,7 +55,7 @@ static inline struct nsproxy *create_nsproxy(void)
nsproxy = kmem_cache_alloc(nsproxy_cachep, GFP_KERNEL); nsproxy = kmem_cache_alloc(nsproxy_cachep, GFP_KERNEL);
if (nsproxy) if (nsproxy)
atomic_set(&nsproxy->count, 1); refcount_set(&nsproxy->count, 1);
return nsproxy; return nsproxy;
} }
......
...@@ -1673,10 +1673,15 @@ menu "Debug kernel data structures" ...@@ -1673,10 +1673,15 @@ menu "Debug kernel data structures"
config DEBUG_LIST config DEBUG_LIST
bool "Debug linked list manipulation" bool "Debug linked list manipulation"
depends on DEBUG_KERNEL || BUG_ON_DATA_CORRUPTION depends on DEBUG_KERNEL
select LIST_HARDENED
help help
Enable this to turn on extended checks in the linked-list Enable this to turn on extended checks in the linked-list walking
walking routines. routines.
This option trades better quality error reports for performance, and
is more suitable for kernel debugging. If you care about performance,
you should only enable CONFIG_LIST_HARDENED instead.
If unsure, say N. If unsure, say N.
...@@ -1710,16 +1715,6 @@ config DEBUG_NOTIFIERS ...@@ -1710,16 +1715,6 @@ config DEBUG_NOTIFIERS
This is a relatively cheap check but if you care about maximum This is a relatively cheap check but if you care about maximum
performance, say N. performance, say N.
config BUG_ON_DATA_CORRUPTION
bool "Trigger a BUG when data corruption is detected"
select DEBUG_LIST
help
Select this option if the kernel should BUG when it encounters
data corruption in kernel memory structures when they get checked
for validity.
If unsure, say N.
config DEBUG_MAPLE_TREE config DEBUG_MAPLE_TREE
bool "Debug maple trees" bool "Debug maple trees"
depends on DEBUG_KERNEL depends on DEBUG_KERNEL
......
...@@ -13,7 +13,7 @@ menuconfig UBSAN ...@@ -13,7 +13,7 @@ menuconfig UBSAN
if UBSAN if UBSAN
config UBSAN_TRAP config UBSAN_TRAP
bool "On Sanitizer warnings, abort the running kernel code" bool "Abort on Sanitizer warnings (smaller kernel but less verbose)"
depends on !COMPILE_TEST depends on !COMPILE_TEST
help help
Building kernels with Sanitizer features enabled tends to grow Building kernels with Sanitizer features enabled tends to grow
...@@ -26,6 +26,14 @@ config UBSAN_TRAP ...@@ -26,6 +26,14 @@ config UBSAN_TRAP
the system. For some system builders this is an acceptable the system. For some system builders this is an acceptable
trade-off. trade-off.
Also note that selecting Y will cause your kernel to Oops
with an "illegal instruction" error with no further details
when a UBSAN violation occurs. (Except on arm64, which will
report which Sanitizer failed.) This may make it hard to
determine whether an Oops was caused by UBSAN or to figure
out the details of a UBSAN violation. It makes the kernel log
output less useful for bug reports.
config CC_HAS_UBSAN_BOUNDS_STRICT config CC_HAS_UBSAN_BOUNDS_STRICT
def_bool $(cc-option,-fsanitize=bounds-strict) def_bool $(cc-option,-fsanitize=bounds-strict)
help help
......
...@@ -167,7 +167,7 @@ obj-$(CONFIG_BTREE) += btree.o ...@@ -167,7 +167,7 @@ obj-$(CONFIG_BTREE) += btree.o
obj-$(CONFIG_INTERVAL_TREE) += interval_tree.o obj-$(CONFIG_INTERVAL_TREE) += interval_tree.o
obj-$(CONFIG_ASSOCIATIVE_ARRAY) += assoc_array.o obj-$(CONFIG_ASSOCIATIVE_ARRAY) += assoc_array.o
obj-$(CONFIG_DEBUG_PREEMPT) += smp_processor_id.o obj-$(CONFIG_DEBUG_PREEMPT) += smp_processor_id.o
obj-$(CONFIG_DEBUG_LIST) += list_debug.o obj-$(CONFIG_LIST_HARDENED) += list_debug.o
obj-$(CONFIG_DEBUG_OBJECTS) += debugobjects.o obj-$(CONFIG_DEBUG_OBJECTS) += debugobjects.o
obj-$(CONFIG_BITREVERSE) += bitrev.o obj-$(CONFIG_BITREVERSE) += bitrev.o
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
* Copyright 2006, Red Hat, Inc., Dave Jones * Copyright 2006, Red Hat, Inc., Dave Jones
* Released under the General Public License (GPL). * Released under the General Public License (GPL).
* *
* This file contains the linked list validation for DEBUG_LIST. * This file contains the linked list validation and error reporting for
* LIST_HARDENED and DEBUG_LIST.
*/ */
#include <linux/export.h> #include <linux/export.h>
...@@ -17,8 +18,9 @@ ...@@ -17,8 +18,9 @@
* attempt). * attempt).
*/ */
bool __list_add_valid(struct list_head *new, struct list_head *prev, __list_valid_slowpath
struct list_head *next) bool __list_add_valid_or_report(struct list_head *new, struct list_head *prev,
struct list_head *next)
{ {
if (CHECK_DATA_CORRUPTION(prev == NULL, if (CHECK_DATA_CORRUPTION(prev == NULL,
"list_add corruption. prev is NULL.\n") || "list_add corruption. prev is NULL.\n") ||
...@@ -37,9 +39,10 @@ bool __list_add_valid(struct list_head *new, struct list_head *prev, ...@@ -37,9 +39,10 @@ bool __list_add_valid(struct list_head *new, struct list_head *prev,
return true; return true;
} }
EXPORT_SYMBOL(__list_add_valid); EXPORT_SYMBOL(__list_add_valid_or_report);
bool __list_del_entry_valid(struct list_head *entry) __list_valid_slowpath
bool __list_del_entry_valid_or_report(struct list_head *entry)
{ {
struct list_head *prev, *next; struct list_head *prev, *next;
...@@ -65,6 +68,5 @@ bool __list_del_entry_valid(struct list_head *entry) ...@@ -65,6 +68,5 @@ bool __list_del_entry_valid(struct list_head *entry)
return false; return false;
return true; return true;
} }
EXPORT_SYMBOL(__list_del_entry_valid); EXPORT_SYMBOL(__list_del_entry_valid_or_report);
...@@ -440,4 +440,8 @@ static inline void debug_gimple_stmt(const_gimple s) ...@@ -440,4 +440,8 @@ static inline void debug_gimple_stmt(const_gimple s)
#define SET_DECL_MODE(decl, mode) DECL_MODE(decl) = (mode) #define SET_DECL_MODE(decl, mode) DECL_MODE(decl) = (mode)
#endif #endif
#if BUILDING_GCC_VERSION >= 14000
#define last_stmt(x) last_nondebug_stmt(x)
#endif
#endif #endif
...@@ -279,6 +279,29 @@ config ZERO_CALL_USED_REGS ...@@ -279,6 +279,29 @@ config ZERO_CALL_USED_REGS
endmenu endmenu
menu "Hardening of kernel data structures"
config LIST_HARDENED
bool "Check integrity of linked list manipulation"
help
Minimal integrity checking in the linked-list manipulation routines
to catch memory corruptions that are not guaranteed to result in an
immediate access fault.
If unsure, say N.
config BUG_ON_DATA_CORRUPTION
bool "Trigger a BUG when data corruption is detected"
select LIST_HARDENED
help
Select this option if the kernel should BUG when it encounters
data corruption in kernel memory structures when they get checked
for validity.
If unsure, say N.
endmenu
config CC_HAS_RANDSTRUCT config CC_HAS_RANDSTRUCT
def_bool $(cc-option,-frandomize-layout-seed-file=/dev/null) def_bool $(cc-option,-frandomize-layout-seed-file=/dev/null)
# Randstruct was first added in Clang 15, but it isn't safe to use until # Randstruct was first added in Clang 15, but it isn't safe to use until
......
...@@ -68,7 +68,7 @@ enum policy_rule_list { IMA_DEFAULT_POLICY = 1, IMA_CUSTOM_POLICY }; ...@@ -68,7 +68,7 @@ enum policy_rule_list { IMA_DEFAULT_POLICY = 1, IMA_CUSTOM_POLICY };
struct ima_rule_opt_list { struct ima_rule_opt_list {
size_t count; size_t count;
char *items[]; char *items[] __counted_by(count);
}; };
/* /*
...@@ -342,6 +342,7 @@ static struct ima_rule_opt_list *ima_alloc_rule_opt_list(const substring_t *src) ...@@ -342,6 +342,7 @@ static struct ima_rule_opt_list *ima_alloc_rule_opt_list(const substring_t *src)
kfree(src_copy); kfree(src_copy);
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
} }
opt_list->count = count;
/* /*
* strsep() has already replaced all instances of '|' with '\0', * strsep() has already replaced all instances of '|' with '\0',
...@@ -357,7 +358,6 @@ static struct ima_rule_opt_list *ima_alloc_rule_opt_list(const substring_t *src) ...@@ -357,7 +358,6 @@ static struct ima_rule_opt_list *ima_alloc_rule_opt_list(const substring_t *src)
opt_list->items[i] = cur; opt_list->items[i] = cur;
cur = strchr(cur, '\0') + 1; cur = strchr(cur, '\0') + 1;
} }
opt_list->count = count;
return opt_list; return opt_list;
} }
......
...@@ -336,6 +336,7 @@ static int read_trusted_verity_root_digests(unsigned int fd) ...@@ -336,6 +336,7 @@ static int read_trusted_verity_root_digests(unsigned int fd)
rc = -ENOMEM; rc = -ENOMEM;
goto err; goto err;
} }
trd->len = len;
if (hex2bin(trd->data, d, len)) { if (hex2bin(trd->data, d, len)) {
kfree(trd); kfree(trd);
...@@ -343,8 +344,6 @@ static int read_trusted_verity_root_digests(unsigned int fd) ...@@ -343,8 +344,6 @@ static int read_trusted_verity_root_digests(unsigned int fd)
goto err; goto err;
} }
trd->len = len;
list_add_tail(&trd->node, &dm_verity_loadpin_trusted_root_digests); list_add_tail(&trd->node, &dm_verity_loadpin_trusted_root_digests);
} }
......
...@@ -938,7 +938,11 @@ void __wait_for_test(struct __test_metadata *t) ...@@ -938,7 +938,11 @@ void __wait_for_test(struct __test_metadata *t)
fprintf(TH_LOG_STREAM, fprintf(TH_LOG_STREAM,
"# %s: Test terminated by timeout\n", t->name); "# %s: Test terminated by timeout\n", t->name);
} else if (WIFEXITED(status)) { } else if (WIFEXITED(status)) {
if (t->termsig != -1) { if (WEXITSTATUS(status) == 255) {
/* SKIP */
t->passed = 1;
t->skip = 1;
} else if (t->termsig != -1) {
t->passed = 0; t->passed = 0;
fprintf(TH_LOG_STREAM, fprintf(TH_LOG_STREAM,
"# %s: Test exited normally instead of by signal (code: %d)\n", "# %s: Test exited normally instead of by signal (code: %d)\n",
...@@ -950,11 +954,6 @@ void __wait_for_test(struct __test_metadata *t) ...@@ -950,11 +954,6 @@ void __wait_for_test(struct __test_metadata *t)
case 0: case 0:
t->passed = 1; t->passed = 1;
break; break;
/* SKIP */
case 255:
t->passed = 1;
t->skip = 1;
break;
/* Other failure, assume step report. */ /* Other failure, assume step report. */
default: default:
t->passed = 0; t->passed = 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment