diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-22 15:01:57 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-22 15:01:57 -1000 |
| commit | 864c07de0c7dcbca58b61625dfa2e461ae63b242 (patch) | |
| tree | 4d2c5f0962b95b99c8f5b04f62ff729086ac5f34 | |
| parent | 5b193d6a8973655f19de87a7873ce31dee2d5fe6 (diff) | |
| parent | 2f70a9b7e9c74b92a85a2584090f0f5c4cbaa4be (diff) | |
Merge tag 'hardening-v7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux
Pull hardening updates from Kees Cook:
- Provide kmalloc_obj*-style API for devm_kmalloc()
- Improve seq_buf API for saner C String handling with several bug
fixes along the way (Kees Cook, Bill Wendling)
- Overflow helper readability improvements (Bill Wendling)
- Unexport native_write_cr4() (Dennis Chen)
- um: fix CONFIG_GCOV for built-in code (Johannes Berg)
- Improve stackinit test coverage (Justin Stitt)
- KMSAN: fix memset() when using fortify-source, again (Arnd Bergmann)
- lkdtm: use kmalloc() instead of __get_free_page (Mahad Ibrahim)
- signalfd: don't dequeue the forced fatal signals (Oleg Nesterov)
- Fix randstruct to recognize new container_of() behavior
- coccinelle: kmalloc_obj: Convert multi-byte integral allocations too
- Fix a couple remaining allocation return type mismatches
* tag 'hardening-v7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux: (29 commits)
overflow: Match arithmetic in both parts of __builtin_choose_expr
lkdtm: Make UNSET_SMEP built-in-only and unexport native_write_cr4()
extcon: realtek: Remove const from type_c_cfg allocation type
coresight: cti: Add const to con_groups allocation type
docs: core-api: Document the seq_buf API
nvdimm: ndtest: Return the string length from flags_show()
powerpc/papr_scm: Return the string length from the sysfs show functions
seq_buf: Add seq_buf_init_append()
bpf: Remove dead newline stripping from format_disasm_line()
seq_buf: Add seq_buf_terminate()
seq_buf: Add seq_buf_strlen()
seq_buf: Clear what a writer did not claim when a seq_buf overflows
seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem() overflow
scsi: lpfc: Print rx monitor records straight into the seq_buf
seq_buf: Do not pop from an overflowed seq_buf
seq_buf: Do not print an empty line from an overflowed seq_buf_do_printk()
.get_maintainer.ignore: Add Kees Cook
devres: Provide kmalloc_obj*-style API for devm_kmalloc
mm: Move default_gfp() into gfp_types.h
kunit/stackinit: Clean up XFAIL under instrumentation
...
28 files changed, 1145 insertions, 145 deletions
diff --git a/.get_maintainer.ignore b/.get_maintainer.ignore index d19b725fd803..a5946cb30cf9 100644 --- a/.get_maintainer.ignore +++ b/.get_maintainer.ignore @@ -6,6 +6,7 @@ Christoph Hellwig <hch@lst.de> Jeff Kirsher <jeffrey.t.kirsher@intel.com> Johannes Berg <johannes.berg@intel.com> Johannes Berg <johannes@sipsolutions.net> +Kees Cook <kees@kernel.org> Marc Gonzalez <marc.w.gonzalez@free.fr> Nathan Chancellor <nathan@kernel.org> Ralf Baechle <ralf@linux-mips.org> diff --git a/Documentation/core-api/kernel-api.rst b/Documentation/core-api/kernel-api.rst index 4c4a57c1c094..f5a0aedbbb48 100644 --- a/Documentation/core-api/kernel-api.rst +++ b/Documentation/core-api/kernel-api.rst @@ -96,6 +96,15 @@ Error Pointers .. kernel-doc:: include/linux/err.h :internal: +Sequence Buffers +---------------- + +.. kernel-doc:: include/linux/seq_buf.h + :internal: + +.. kernel-doc:: lib/seq_buf.c + :no-identifiers: seq_buf_can_fit + Sorting ------- diff --git a/MAINTAINERS b/MAINTAINERS index 5b70859d117b..7ffd35e7faf0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14231,6 +14231,7 @@ F: include/linux/ucopysize.h F: kernel/configs/hardening.config F: kernel/kstack_erase.c F: lib/tests/randstruct_kunit.c +F: lib/tests/stackinit_kunit.c F: lib/tests/usercopy_kunit.c F: mm/usercopy.c F: scripts/Makefile.kstack_erase diff --git a/arch/powerpc/platforms/pseries/papr_scm.c b/arch/powerpc/platforms/pseries/papr_scm.c index 75da96c08cdd..5c3bf06bb59b 100644 --- a/arch/powerpc/platforms/pseries/papr_scm.c +++ b/arch/powerpc/platforms/pseries/papr_scm.c @@ -1108,7 +1108,7 @@ static ssize_t perf_stats_show(struct device *dev, free_stats: kfree(stats); - return rc ? rc : (ssize_t)seq_buf_used(&s); + return rc ?: (ssize_t)seq_buf_strlen(&s); } static DEVICE_ATTR_ADMIN_RO(perf_stats); @@ -1147,10 +1147,10 @@ static ssize_t flags_show(struct device *dev, if (health & PAPR_PMEM_SCRUBBED_AND_LOCKED) seq_buf_printf(&s, "scrubbed locked "); - if (seq_buf_used(&s)) + if (seq_buf_strlen(&s)) seq_buf_printf(&s, "\n"); - return seq_buf_used(&s); + return seq_buf_strlen(&s); } DEVICE_ATTR_RO(flags); diff --git a/arch/um/include/asm/common.lds.S b/arch/um/include/asm/common.lds.S index 6585cb93e6eb..7b0c6f8a9d4f 100644 --- a/arch/um/include/asm/common.lds.S +++ b/arch/um/include/asm/common.lds.S @@ -93,6 +93,7 @@ .fini_array : { __fini_array_start = .; *(.fini_array) + *(.fini_array.*) __fini_array_end = .; } diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c index 7d0b9bdc64cc..9bd50c68cd7e 100644 --- a/arch/x86/kernel/cpu/common.c +++ b/arch/x86/kernel/cpu/common.c @@ -490,9 +490,6 @@ set_register: bits_changed); } } -#if IS_MODULE(CONFIG_LKDTM) -EXPORT_SYMBOL_GPL(native_write_cr4); -#endif void cr4_update_irqsoff(unsigned long set, unsigned long clear) { diff --git a/drivers/extcon/extcon-rtk-type-c.c b/drivers/extcon/extcon-rtk-type-c.c index 82b60b927e41..da0b3fd3676e 100644 --- a/drivers/extcon/extcon-rtk-type-c.c +++ b/drivers/extcon/extcon-rtk-type-c.c @@ -1368,7 +1368,8 @@ static int extcon_rtk_type_c_probe(struct platform_device *pdev) goto err; } - type_c->type_c_cfg = devm_kzalloc(dev, sizeof(*type_c_cfg), GFP_KERNEL); + type_c->type_c_cfg = devm_kzalloc(dev, sizeof(*type_c->type_c_cfg), + GFP_KERNEL); if (!type_c->type_c_cfg) return -ENOMEM; diff --git a/drivers/hwtracing/coresight/coresight-cti-sysfs.c b/drivers/hwtracing/coresight/coresight-cti-sysfs.c index 3fe2c916d228..2ed5d8eb1dda 100644 --- a/drivers/hwtracing/coresight/coresight-cti-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-cti-sysfs.c @@ -1132,7 +1132,7 @@ static int cti_create_cons_groups(struct device *dev, struct cti_device *ctidev) /* nr groups = dynamic + static + NULL terminator */ nr_groups = ctidev->nr_trig_con + CORESIGHT_CTI_STATIC_GROUPS_MAX; ctidev->con_groups = devm_kcalloc(dev, nr_groups, - sizeof(struct attribute_group *), + sizeof(*ctidev->con_groups), GFP_KERNEL); if (!ctidev->con_groups) return -ENOMEM; diff --git a/drivers/misc/lkdtm/bugs.c b/drivers/misc/lkdtm/bugs.c index 3eca2ef64aff..d82c47fb9f03 100644 --- a/drivers/misc/lkdtm/bugs.c +++ b/drivers/misc/lkdtm/bugs.c @@ -676,7 +676,7 @@ static void lkdtm_STACK_GUARD_PAGE_TRAILING(void) static void lkdtm_UNSET_SMEP(void) { -#if IS_ENABLED(CONFIG_X86_64) && !IS_ENABLED(CONFIG_UML) +#if IS_ENABLED(CONFIG_X86_64) && !IS_ENABLED(CONFIG_UML) && IS_BUILTIN(CONFIG_LKDTM) #define MOV_CR4_DEPTH 64 void (*direct_write_cr4)(unsigned long val); unsigned char *insn; @@ -737,7 +737,7 @@ static void lkdtm_UNSET_SMEP(void) native_write_cr4(cr4); } #else - pr_err("XFAIL: this test is x86_64-only\n"); + pr_err("XFAIL: this test is x86_64-only and requires CONFIG_LKDTM=y\n"); #endif } diff --git a/drivers/misc/lkdtm/core.c b/drivers/misc/lkdtm/core.c index ededa32d6744..01bebcb33bd4 100644 --- a/drivers/misc/lkdtm/core.c +++ b/drivers/misc/lkdtm/core.c @@ -236,11 +236,11 @@ static ssize_t lkdtm_debugfs_entry(struct file *f, if (count >= PAGE_SIZE) return -EINVAL; - buf = (char *)__get_free_page(GFP_KERNEL); + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; if (copy_from_user(buf, user_buf, count)) { - free_page((unsigned long) buf); + kfree(buf); return -EFAULT; } /* NULL-terminate and remove enter */ @@ -248,7 +248,7 @@ static ssize_t lkdtm_debugfs_entry(struct file *f, strim(buf); crashtype = find_crashtype(buf); - free_page((unsigned long)buf); + kfree(buf); if (!crashtype) return -EINVAL; @@ -271,7 +271,7 @@ static ssize_t lkdtm_debugfs_read(struct file *f, char __user *user_buf, ssize_t out; char *buf; - buf = (char *)__get_free_page(GFP_KERNEL); + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (buf == NULL) return -ENOMEM; @@ -290,7 +290,7 @@ static ssize_t lkdtm_debugfs_read(struct file *f, char __user *user_buf, out = simple_read_from_buffer(user_buf, count, off, buf, n); - free_page((unsigned long) buf); + kfree(buf); return out; } @@ -313,11 +313,11 @@ static ssize_t direct_entry(struct file *f, const char __user *user_buf, if (count < 1) return -EINVAL; - buf = (char *)__get_free_page(GFP_KERNEL); + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; if (copy_from_user(buf, user_buf, count)) { - free_page((unsigned long) buf); + kfree(buf); return -EFAULT; } /* NULL-terminate and remove enter */ @@ -325,7 +325,7 @@ static ssize_t direct_entry(struct file *f, const char __user *user_buf, strim(buf); crashtype = find_crashtype(buf); - free_page((unsigned long) buf); + kfree(buf); if (!crashtype) return -EINVAL; diff --git a/drivers/scsi/lpfc/lpfc_sli.c b/drivers/scsi/lpfc/lpfc_sli.c index cfa4371169f1..a7b1ea67ed98 100644 --- a/drivers/scsi/lpfc/lpfc_sli.c +++ b/drivers/scsi/lpfc/lpfc_sli.c @@ -8108,7 +8108,6 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba, spinlock_t *ring_lock = &rx_monitor->lock; u32 ring_size = rx_monitor->entries; u32 cnt = 0; - char tmp[DBG_LOG_STR_SZ] = {0}; bool log_to_kmsg = (!buf || !buf_len) ? true : false; struct seq_buf s; @@ -8133,27 +8132,37 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba, /* Read out this entry's data. */ if (!log_to_kmsg) { + unsigned int len; + + /* A header that did not fit leaves no room. */ + if (seq_buf_has_overflowed(&s)) + break; + len = seq_buf_used(&s); + + seq_buf_printf(&s, + "%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n", + *head_idx, + entry->max_bytes_per_interval, + entry->cmf_bytes, + entry->total_bytes, + entry->rcv_bytes, + entry->avg_io_latency, + entry->avg_io_size, + entry->max_read_cnt, + entry->cmf_busy, entry->io_cnt, + entry->cmf_info, + entry->timer_utilization, + entry->timer_interval); + /* * Drop a record whole if it does not fit, without - * consuming its ring entry. + * consuming its ring entry: the reader takes the + * string up to its NUL. */ - scnprintf(tmp, sizeof(tmp), - "%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n", - *head_idx, - entry->max_bytes_per_interval, - entry->cmf_bytes, - entry->total_bytes, - entry->rcv_bytes, - entry->avg_io_latency, - entry->avg_io_size, - entry->max_read_cnt, - entry->cmf_busy, entry->io_cnt, - entry->cmf_info, - entry->timer_utilization, - entry->timer_interval); - - if (seq_buf_puts(&s, tmp) < 0) + if (seq_buf_has_overflowed(&s)) { + buf[len] = '\0'; break; + } } else { lpfc_printf_log(phba, KERN_INFO, LOG_CGN_MGMT, "4410 %02u: MBPI %llu Xmit %llu " diff --git a/fs/signalfd.c b/fs/signalfd.c index dff53745e352..22bc0870a824 100644 --- a/fs/signalfd.c +++ b/fs/signalfd.c @@ -48,17 +48,30 @@ static int signalfd_release(struct inode *inode, struct file *file) return 0; } +static void refine_sigmask(struct signalfd_ctx *ctx, sigset_t *sigmask) +{ + struct k_sigaction *k = current->sighand->action; + int n; + + *sigmask = ctx->sigmask; + for (n = 1; n <= _NSIG; ++n, ++k) { + if (k->sa.sa_flags & SA_IMMUTABLE) + sigaddset(sigmask, n); + } +} + static __poll_t signalfd_poll(struct file *file, poll_table *wait) { struct signalfd_ctx *ctx = file->private_data; __poll_t events = 0; + sigset_t sigmask; poll_wait(file, ¤t->sighand->signalfd_wqh, wait); spin_lock_irq(¤t->sighand->siglock); - if (next_signal(¤t->pending, &ctx->sigmask) || - next_signal(¤t->signal->shared_pending, - &ctx->sigmask)) + refine_sigmask(ctx, &sigmask); + if (next_signal(¤t->pending, &sigmask) || + next_signal(¤t->signal->shared_pending, &sigmask)) events |= EPOLLIN; spin_unlock_irq(¤t->sighand->siglock); @@ -155,11 +168,13 @@ static ssize_t signalfd_dequeue(struct signalfd_ctx *ctx, kernel_siginfo_t *info int nonblock) { enum pid_type type; - ssize_t ret; DECLARE_WAITQUEUE(wait, current); + sigset_t sigmask; + ssize_t ret; spin_lock_irq(¤t->sighand->siglock); - ret = dequeue_signal(&ctx->sigmask, info, &type); + refine_sigmask(ctx, &sigmask); + ret = dequeue_signal(&sigmask, info, &type); switch (ret) { case 0: if (!nonblock) @@ -174,7 +189,7 @@ static ssize_t signalfd_dequeue(struct signalfd_ctx *ctx, kernel_siginfo_t *info add_wait_queue(¤t->sighand->signalfd_wqh, &wait); for (;;) { set_current_state(TASK_INTERRUPTIBLE); - ret = dequeue_signal(&ctx->sigmask, info, &type); + ret = dequeue_signal(&sigmask, info, &type); if (ret != 0) break; if (signal_pending(current)) { @@ -184,6 +199,7 @@ static ssize_t signalfd_dequeue(struct signalfd_ctx *ctx, kernel_siginfo_t *info spin_unlock_irq(¤t->sighand->siglock); schedule(); spin_lock_irq(¤t->sighand->siglock); + refine_sigmask(ctx, &sigmask); } spin_unlock_irq(¤t->sighand->siglock); diff --git a/include/linux/device/devres.h b/include/linux/device/devres.h index 14ab9159bdda..7656c9ecd4da 100644 --- a/include/linux/device/devres.h +++ b/include/linux/device/devres.h @@ -62,6 +62,108 @@ static inline void *devm_kcalloc(struct device *dev, size_t n, size_t size, gfp_ { return devm_kmalloc_array(dev, n, size, flags | __GFP_ZERO); } + +/** + * __devm_alloc_objs - Device-managed allocation of objects of a given type + * @DEVM_ALLOC: which device-managed size-based allocator to use. + * @DEV: device to scope the allocation lifetime to. + * @GFP: GFP flags for the allocation. + * @TYPE: type to allocate space for. + * @COUNT: how many @TYPE objects to allocate. + * + * Device-managed counterpart of __alloc_objs(). + * @DEVM_ALLOC takes (@DEV, size, gfp). + * + * Returns: Newly allocated pointer to (first) @TYPE of @COUNT-many allocated + * @TYPE objects, or NULL on failure. + */ +#define __devm_alloc_objs(DEVM_ALLOC, DEV, GFP, TYPE, COUNT) \ +({ \ + const size_t __obj_size = size_mul(sizeof(TYPE), COUNT); \ + (TYPE *)DEVM_ALLOC(DEV, __obj_size, GFP); \ +}) + +/** + * devm_kmalloc_obj - Device-managed allocation of a single typed object + * @DEV: Device to scope the allocation lifetime to. + * @VAR_OR_TYPE: Variable or type to allocate. + * @GFP: Optional GFP flags (defaults to %GFP_KERNEL). + * + * Device-managed counterpart of kmalloc_obj(): the allocation is sized from + * @VAR_OR_TYPE and returned as a pointer to that type, not void *. + * + * Returns: newly allocated pointer to a @VAR_OR_TYPE on success, or NULL on + * failure. + */ +#define devm_kmalloc_obj(DEV, VAR_OR_TYPE, ...) \ + __devm_alloc_objs(devm_kmalloc, DEV, default_gfp(__VA_ARGS__), \ + typeof(VAR_OR_TYPE), 1) + +/** + * devm_kmalloc_objs - Device-managed allocation of an array of a typed object + * @DEV: Device to scope the allocation lifetime to. + * @VAR_OR_TYPE: Variable or type to allocate an array of. + * @COUNT: How many elements in the array. + * @GFP: Optional GFP flags (defaults to %GFP_KERNEL). + * + * Returns: newly allocated pointer to an array of @VAR_OR_TYPE on success, or + * NULL on failure (including when the total size overflows). + */ +#define devm_kmalloc_objs(DEV, VAR_OR_TYPE, COUNT, ...) \ + __devm_alloc_objs(devm_kmalloc, DEV, default_gfp(__VA_ARGS__), \ + typeof(VAR_OR_TYPE), COUNT) + +/** + * __devm_alloc_flex - Device-managed allocation of a trailing-flex-array object + * @DEVM_ALLOC: which device-managed allocator to use. + * @DEV: device to scope the allocation lifetime to. + * @GFP: GFP flags for the allocation. + * @TYPE: type of structure to allocate space for. + * @FAM: name of the flexible array member of @TYPE. + * @COUNT: how many @FAM elements to allocate space for. + * + * Device-managed counterpart of __alloc_flex(). + * @DEVM_ALLOC takes (@DEV, size, gfp). + * + * Returns: Newly allocated pointer to @TYPE with @COUNT-many trailing @FAM + * elements, or NULL on failure. + */ +#define __devm_alloc_flex(DEVM_ALLOC, DEV, GFP, TYPE, FAM, COUNT) \ +({ \ + const size_t __count = (COUNT); \ + const size_t __obj_size = struct_size_t(TYPE, FAM, __count); \ + TYPE *__obj_ptr = DEVM_ALLOC(DEV, __obj_size, GFP); \ + if (__obj_ptr) \ + __set_flex_counter(__obj_ptr->FAM, __count); \ + __obj_ptr; \ +}) + +/** + * devm_kmalloc_flex - Device-managed allocation of a flexible-array structure + * @DEV: Device to scope the allocation lifetime to. + * @VAR_OR_TYPE: Variable or type to allocate (with its flexible array). + * @FAM: The name of the flexible array member of the structure. + * @COUNT: How many flexible array member elements to allocate. + * @GFP: Optional GFP flags (defaults to %GFP_KERNEL). + * + * Returns: newly allocated pointer to @VAR_OR_TYPE on success, or NULL on + * failure. If @FAM has been annotated with __counted_by(), its counter is set + * to @COUNT. + */ +#define devm_kmalloc_flex(DEV, VAR_OR_TYPE, FAM, COUNT, ...) \ + __devm_alloc_flex(devm_kmalloc, DEV, default_gfp(__VA_ARGS__), \ + typeof(VAR_OR_TYPE), FAM, COUNT) + +/* All devm_kzalloc aliases for devm_kmalloc_(obj|objs|flex). */ +#define devm_kzalloc_obj(DEV, P, ...) \ + __devm_alloc_objs(devm_kzalloc, DEV, default_gfp(__VA_ARGS__), typeof(P), 1) +#define devm_kzalloc_objs(DEV, P, COUNT, ...) \ + __devm_alloc_objs(devm_kzalloc, DEV, default_gfp(__VA_ARGS__), \ + typeof(P), COUNT) +#define devm_kzalloc_flex(DEV, P, FAM, COUNT, ...) \ + __devm_alloc_flex(devm_kzalloc, DEV, default_gfp(__VA_ARGS__), \ + typeof(P), FAM, COUNT) + static inline __realloc_size(3, 4) void * __must_check devm_krealloc_array(struct device *dev, void *p, size_t new_n, size_t new_size, gfp_t flags) { diff --git a/include/linux/fortify-string.h b/include/linux/fortify-string.h index cf841dc71fef..7e7c369e0a6c 100644 --- a/include/linux/fortify-string.h +++ b/include/linux/fortify-string.h @@ -458,10 +458,8 @@ __FORTIFY_INLINE bool fortify_memset_chk(__kernel_size_t size, * __struct_size() vs __member_size() must be captured here to avoid * evaluating argument side-effects further into the macro layers. */ -#ifndef CONFIG_KMSAN #define memset(p, c, s) __fortify_memset_chk(p, c, s, \ __struct_size(p), __member_size(p)) -#endif /* * To make sure the compiler can enforce protection against buffer overflows, diff --git a/include/linux/gfp.h b/include/linux/gfp.h index 872bc53f32ec..b62e2fd53cb4 100644 --- a/include/linux/gfp.h +++ b/include/linux/gfp.h @@ -13,10 +13,6 @@ struct vm_area_struct; struct mempolicy; -/* Helper macro to avoid gfp flags if they are the default one */ -#define __default_gfp(a,b,...) b -#define default_gfp(...) __default_gfp(,##__VA_ARGS__,GFP_KERNEL) - static inline bool gfpflags_allow_blocking(const gfp_t gfp_flags) { return !!(gfp_flags & __GFP_DIRECT_RECLAIM); diff --git a/include/linux/gfp_types.h b/include/linux/gfp_types.h index 190191411009..b1cd143f3c42 100644 --- a/include/linux/gfp_types.h +++ b/include/linux/gfp_types.h @@ -382,4 +382,8 @@ enum { __GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM) #define GFP_TRANSHUGE (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM) +/* Helper macro to avoid gfp flags if they are the default one */ +#define __default_gfp(a,b,...) b +#define default_gfp(...) __default_gfp(,##__VA_ARGS__,GFP_KERNEL) + #endif /* __LINUX_GFP_TYPES_H */ diff --git a/include/linux/overflow.h b/include/linux/overflow.h index a8cb6319b4fb..d0f04436343e 100644 --- a/include/linux/overflow.h +++ b/include/linux/overflow.h @@ -418,7 +418,7 @@ static __always_inline size_t __must_check size_sub(size_t minuend, size_t subtr #define flex_array_size(p, member, count) \ __builtin_choose_expr(__is_constexpr(count), \ (count) * sizeof(*(p)->member) + __must_be_array((p)->member), \ - size_mul(count, sizeof(*(p)->member) + __must_be_array((p)->member))) + size_mul(count, sizeof(*(p)->member)) + __must_be_array((p)->member)) /** * struct_size() - Calculate size of structure with trailing flexible array. diff --git a/include/linux/seq_buf.h b/include/linux/seq_buf.h index 9f2839e73f8a..f006f2654e4c 100644 --- a/include/linux/seq_buf.h +++ b/include/linux/seq_buf.h @@ -5,6 +5,7 @@ #include <linux/bug.h> #include <linux/minmax.h> #include <linux/seq_file.h> +#include <linux/string.h> #include <linux/types.h> /* @@ -30,6 +31,10 @@ struct seq_buf { .size = SIZE, \ } +/** + * seq_buf_clear - reset the seq_buf to be read / appended from the beginning + * @s: the seq_buf handle + */ static inline void seq_buf_clear(struct seq_buf *s) { s->len = 0; @@ -37,6 +42,14 @@ static inline void seq_buf_clear(struct seq_buf *s) s->buffer[0] = '\0'; } +/** + * seq_buf_init - initialize a seq_buf + * @s: the seq_buf handle + * @buf: pointer to the buffer + * @size: total size of @buf + * + * The contents of the buffer are ignored. + */ static inline void seq_buf_init(struct seq_buf *s, char *buf, unsigned int size) { @@ -55,12 +68,46 @@ seq_buf_has_overflowed(struct seq_buf *s) return s->len > s->size; } +/* + * Mark @s as overflowed, which discards the length of what it holds. The + * bytes up to its last one are the string from then on, as that is where + * seq_buf_str() terminates it, so clear whatever was not written: a writer + * sets len to how much it filled, and anything past that was never adopted. + */ static inline void seq_buf_set_overflow(struct seq_buf *s) { + if (s->len < s->size) + memset(s->buffer + s->len, 0, s->size - s->len); + s->len = s->size + 1; } +/** + * seq_buf_init_append - initialize a seq_buf over a buffer that may + * already hold NUL-terminated content + * @s: the seq_buf handle + * @buf: pointer to the (possibly non-empty) buffer + * @size: total size of @buf + * + * Unlike seq_buf_init(), which always starts @buf at len=0, this + * preserves whatever NUL-terminated content @buf already holds and + * positions @s to append after it. Useful for converting code that used + * to append to an existing buffer with strlcat()/scnprintf() and friends. + * + * If @buf holds no NUL within @size, @s starts out overflowed, as + * strlcat() treats such a buffer as already truncated. + */ +static inline void +seq_buf_init_append(struct seq_buf *s, char *buf, unsigned int size) +{ + s->buffer = buf; + s->size = size; + s->len = strnlen(buf, size); + if (s->len == size) + seq_buf_set_overflow(s); +} + /* * How much buffer is left on the seq_buf? */ @@ -79,6 +126,27 @@ static inline unsigned int seq_buf_used(struct seq_buf *s) return min(s->len, s->size); } +/* + * NUL-terminate the buffer in @s: directly after the data when there is + * room for it, otherwise in the last byte of the buffer. @s->size must not + * be zero. + * + * Returns: the offset of the NUL. + */ +static inline size_t __seq_buf_terminate(struct seq_buf *s) +{ + size_t end; + + if (seq_buf_buffer_left(s)) + end = s->len; + else + end = s->size - 1; + + s->buffer[end] = 0; + + return end; +} + /** * seq_buf_str - get NUL-terminated C string from seq_buf * @s: the seq_buf handle @@ -88,7 +156,14 @@ static inline unsigned int seq_buf_used(struct seq_buf *s) * * Note, if this is called when the buffer has overflowed, then * the last byte of the buffer is zeroed, and the len will still - * point passed it. + * point passed it. The same happens when the buffer is exactly + * full: the NUL takes the place of the last byte written, which is + * lost, though seq_buf_used() still counts it. + * + * A zero-sized seq_buf has nowhere to put a NUL, so the empty string + * is returned instead of writing to @s->buffer. Any other seq_buf + * returns @s->buffer, even when it holds an empty string, so callers + * always get their own buffer back. * * After this function is called, s->buffer is safe to use * in string operations. @@ -97,18 +172,68 @@ static inline unsigned int seq_buf_used(struct seq_buf *s) */ static inline const char *seq_buf_str(struct seq_buf *s) { - if (WARN_ON(s->size == 0)) + if (s->size == 0) return ""; - if (seq_buf_buffer_left(s)) - s->buffer[s->len] = 0; - else - s->buffer[s->size - 1] = 0; + __seq_buf_terminate(s); return s->buffer; } /** + * seq_buf_strlen - get the length of the NUL-terminated C string in seq_buf + * @s: the seq_buf handle + * + * This makes sure that the buffer in @s is NUL-terminated, exactly as + * seq_buf_str() does, and returns the length of the resulting string + * without walking it. Unlike seq_buf_used(), this does not count the byte + * given up to the NUL when the buffer is full or has overflowed. When the + * buffer is exactly full, that byte is the last one written, and calling + * either function loses it. + * + * A zero-sized seq_buf holds no string, so 0 is returned without writing + * to @s->buffer, matching what seq_buf_str() returns for one. + * + * After this function is called, s->buffer is safe to use + * in string operations. + * + * Returns: the offset of the NUL that terminates @s->buffer. That is the + * length of the string unless an earlier NUL is in the way, either one the + * data written to @s carried itself, or one seq_buf_set_overflow() left + * behind when it cleared what no writer had claimed. + */ +static inline size_t seq_buf_strlen(struct seq_buf *s) +{ + if (s->size == 0) + return 0; + + return __seq_buf_terminate(s); +} + +/** + * seq_buf_terminate - NUL-terminate the string in a seq_buf + * @s: the seq_buf handle + * + * Terminate @s->buffer exactly as seq_buf_str() and seq_buf_strlen() do, + * for callers that want neither the pointer nor the length and only need + * the buffer to be safe to read as a C string. A zero-sized seq_buf has + * nowhere to put a NUL and is left untouched. + * + * Nothing is returned on purpose: a caller that wants the length should + * use seq_buf_strlen(), which says so. + * + * After this function is called, s->buffer is safe to use + * in string operations. + */ +static inline void seq_buf_terminate(struct seq_buf *s) +{ + if (s->size == 0) + return; + + __seq_buf_terminate(s); +} + +/** * seq_buf_get_buf - get buffer to write arbitrary data to * @s: the seq_buf handle * @bufp: the beginning of the buffer is stored here @@ -155,11 +280,11 @@ static inline void seq_buf_commit(struct seq_buf *s, int num) * * Removes the last written character to the seq_buf @s. * - * Returns the last character or -1 if it is empty. + * Returns the last character, or -1 if @s is empty or has overflowed. */ static inline int seq_buf_pop(struct seq_buf *s) { - if (!s->len) + if (!s->len || seq_buf_has_overflowed(s)) return -1; s->len--; diff --git a/include/linux/trace_seq.h b/include/linux/trace_seq.h index 697d619aafdc..7174ebf3f015 100644 --- a/include/linux/trace_seq.h +++ b/include/linux/trace_seq.h @@ -86,7 +86,10 @@ static inline bool trace_seq_has_overflowed(struct trace_seq *s) * * Removes the last written character to the trace_seq @s. * - * Returns the last character or -1 if it is empty. + * Returns the last character, or -1 if the underlying seq_buf is empty or + * has overflowed. Note that only that buffer is consulted: a @s marked + * full by a write that did not fit, which trace_seq_has_overflowed() + * reports as overflowed, still pops the last character written. */ static inline int trace_seq_pop(struct trace_seq *s) { diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c index 0abbbe177e31..8f64bfd9afee 100644 --- a/kernel/bpf/diagnostics.c +++ b/kernel/bpf/diagnostics.c @@ -351,7 +351,7 @@ static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_mark if (mark.chunk) { mark.chunk->seq.len = mark.len; - seq_buf_str(&mark.chunk->seq); + seq_buf_terminate(&mark.chunk->seq); } } @@ -631,11 +631,7 @@ static void format_disasm_line(struct bpf_verifier_env *env, int insn_idx, return; print_bpf_insn(&cbs, insn, env->allow_ptr_leaks); - seq_buf_str(&ctx.seq); - ctx.seq.len = strnlen(line->text, sizeof(line->text)); - while (ctx.seq.len && line->text[ctx.seq.len - 1] == '\n') - seq_buf_pop(&ctx.seq); - seq_buf_str(&ctx.seq); + seq_buf_terminate(&ctx.seq); line->valid = true; } diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 30c0ddf90887..6428861102a1 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -4908,7 +4908,7 @@ static __init int event_trace_enable(void) */ __trace_early_add_events(tr); - seq_buf_str(&bootup_event_seq); + seq_buf_terminate(&bootup_event_seq); early_enable_events(tr, bootup_event_buf, false); trace_printk_start_comm(); @@ -4937,7 +4937,7 @@ static __init int event_trace_enable_again(void) if (!tr) return -ENODEV; - seq_buf_str(&bootup_event_seq); + seq_buf_terminate(&bootup_event_seq); early_enable_events(tr, bootup_event_buf, true); return 0; diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index dcb5d7321095..5c07fee3a328 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -2985,8 +2985,7 @@ find_synthetic_field_var(struct hist_trigger_data *target_hist_data, seq_buf_init(&s, synthetic_name, MAX_FILTER_STR_VAL); seq_buf_printf(&s, "synthetic_%s", field_name); - /* Terminate synthetic_name with a NUL. */ - seq_buf_str(&s); + seq_buf_terminate(&s); if (seq_buf_has_overflowed(&s)) { kfree(synthetic_name); @@ -3103,8 +3102,7 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data, if (saved_filter) seq_buf_printf(&s, " if %s", saved_filter); - /* Terminate cmd with a NUL. */ - seq_buf_str(&s); + seq_buf_terminate(&s); if (seq_buf_has_overflowed(&s)) { kfree(cmd); diff --git a/lib/seq_buf.c b/lib/seq_buf.c index a92093f346da..54b76044e4ba 100644 --- a/lib/seq_buf.c +++ b/lib/seq_buf.c @@ -76,6 +76,8 @@ int seq_buf_vprintf(struct seq_buf *s, const char *fmt, va_list args) s->len += len; return 0; } + /* vsnprintf() wrote as much as fits, so none of it is stale */ + s->len = s->size; } seq_buf_set_overflow(s); return -1; @@ -128,7 +130,7 @@ void seq_buf_do_printk(struct seq_buf *s, const char *lvl) } /* No trailing LF */ - if (start < s->buffer + s->len) + if (*start) printk("%s%s\n", lvl, start); } EXPORT_SYMBOL_GPL(seq_buf_do_printk); @@ -164,6 +166,8 @@ int seq_buf_bprintf(struct seq_buf *s, const char *fmt, const u32 *binary) s->len += ret; return 0; } + /* bstr_printf() wrote as much as fits, so none of it is stale */ + s->len = s->size; } seq_buf_set_overflow(s); return -1; @@ -175,7 +179,9 @@ int seq_buf_bprintf(struct seq_buf *s, const char *fmt, const u32 *binary) * @s: seq_buf descriptor * @str: simple string to record * - * Copy a simple string into the sequence buffer. + * Copy a simple string into the sequence buffer. If @str does not fit, + * as much of it as fits is copied, followed by a null byte, as + * seq_buf_printf() does. * * Returns: zero on success, -1 on overflow. */ @@ -194,6 +200,11 @@ int seq_buf_puts(struct seq_buf *s, const char *str) s->len += len - 1; return 0; } + /* Copy what fits, so the buffer never holds stale bytes */ + if (s->len < s->size) { + strscpy(s->buffer + s->len, str, s->size - s->len); + s->len = s->size; + } seq_buf_set_overflow(s); return -1; } @@ -229,7 +240,7 @@ EXPORT_SYMBOL_GPL(seq_buf_putc); * * There may be cases where raw memory needs to be written into the * buffer and a strcpy() would not work. Using this function allows - * for such cases. + * for such cases. If @mem does not fit, as much of it as fits is copied. * * Returns: zero on success, -1 on overflow. */ @@ -242,9 +253,15 @@ int seq_buf_putmem(struct seq_buf *s, const void *mem, unsigned int len) s->len += len; return 0; } + /* Copy what fits, so the buffer never holds stale bytes */ + if (s->len < s->size) { + memcpy(s->buffer + s->len, mem, s->size - s->len); + s->len = s->size; + } seq_buf_set_overflow(s); return -1; } +EXPORT_SYMBOL_GPL(seq_buf_putmem); #define MAX_MEMHEX_BYTES 8U #define HEX_CHARS (MAX_MEMHEX_BYTES*2 + 1) @@ -330,6 +347,7 @@ int seq_buf_path(struct seq_buf *s, const struct path *path, const char *esc) return res; } +EXPORT_SYMBOL_GPL(seq_buf_path); /** * seq_buf_to_user - copy the sequence buffer to user space @@ -391,12 +409,13 @@ int seq_buf_to_user(struct seq_buf *s, char __user *ubuf, size_t start, int cnt) * * Function is an analogue of print_hex_dump() and thus has similar interface. * - * linebuf size is maximal length for one line. - * 32 * 3 - maximum bytes per line, each printed into 2 chars + 1 for - * separating space - * 2 - spaces separating hex dump and ASCII representation - * 32 - ASCII representation - * 1 - terminating '\0' + * linebuf size is maximal length for one line:: + * + * 32 * 3 - maximum bytes per line, each printed into 2 chars + 1 for + * separating space + * 2 - spaces separating hex dump and ASCII representation + * 32 - ASCII representation + * 1 - terminating '\0' * * Returns: zero on success, -1 on overflow. */ diff --git a/lib/tests/seq_buf_kunit.c b/lib/tests/seq_buf_kunit.c index eb466386bbef..fc53e9ef844c 100644 --- a/lib/tests/seq_buf_kunit.c +++ b/lib/tests/seq_buf_kunit.c @@ -6,7 +6,11 @@ */ #include <kunit/test.h> +#include <linux/console.h> +#include <linux/fs.h> #include <linux/seq_buf.h> +#include <linux/shmem_fs.h> +#include <linux/string.h> static void seq_buf_init_test(struct kunit *test) { @@ -21,6 +25,75 @@ static void seq_buf_init_test(struct kunit *test) KUNIT_EXPECT_EQ(test, seq_buf_buffer_left(&s), 32); KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 0); KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), ""); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 0); +} + +static void seq_buf_init_append_test(struct kunit *test) +{ + char buf[32] = "hello"; + struct seq_buf s; + + /* Initial string contents match. */ + seq_buf_init_append(&s, buf, sizeof(buf)); + KUNIT_EXPECT_EQ(test, s.size, 32); + KUNIT_EXPECT_EQ(test, s.len, 5); + KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_buffer_left(&s), 32 - 5); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 5); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hello"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 5); + + /* Appending with space works. */ + seq_buf_puts(&s, " world"); + KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 11); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hello world"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 11); + + /* No truncation when space for NUL is present. */ + seq_buf_init_append(&s, buf, 12); + KUNIT_EXPECT_EQ(test, s.size, 12); + KUNIT_EXPECT_EQ(test, s.len, 11); + KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 11); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hello world"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 11); + + /* + * No NUL within the size: the content is already truncated, as + * strlcat() would see it, so @s starts out overflowed. The content + * stays, and appending adds nothing. + */ + seq_buf_init_append(&s, buf, 11); + KUNIT_EXPECT_EQ(test, s.size, 11); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_buffer_left(&s), 0); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 11); + KUNIT_EXPECT_MEMEQ(test, buf, "hello world", 11); + seq_buf_puts(&s, "!"); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_MEMEQ(test, buf, "hello world", 11); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hello worl"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 10); + + /* + * The size bounds the scan: the string runs past it, so there is + * no NUL within the size either. + */ + seq_buf_init_append(&s, buf, 5); + KUNIT_EXPECT_EQ(test, s.size, 5); + /* Overflowed at the size; an unbounded scan would find more. */ + KUNIT_EXPECT_EQ(test, s.len, 5 + 1); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_buffer_left(&s), 0); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hell"); + + /* With no room even for a NUL, @s is overflowed and @buf untouched. */ + seq_buf_init_append(&s, buf, 0); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 0); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), ""); + KUNIT_EXPECT_EQ(test, buf[0], 'h'); } static void seq_buf_declare_test(struct kunit *test) @@ -113,6 +186,47 @@ static void seq_buf_putc_test(struct kunit *test) KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), ""); } +static void seq_buf_pop_test(struct kunit *test) +{ + DECLARE_SEQ_BUF(s, 8); + struct seq_buf t; + char *buf; + + /* Nothing to pop. */ + KUNIT_EXPECT_EQ(test, seq_buf_pop(&s), -1); + KUNIT_EXPECT_EQ(test, s.len, 0); + + seq_buf_puts(&s, "hello"); + KUNIT_EXPECT_EQ(test, seq_buf_pop(&s), 'o'); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 4); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hell"); + + /* A 0xff byte must not be mistaken for an empty buffer. */ + seq_buf_putc(&s, 0xff); + KUNIT_EXPECT_EQ(test, seq_buf_pop(&s), 0xff); + + /* A full buffer pops its last byte. */ + seq_buf_puts(&s, "abc"); + seq_buf_putc(&s, 'd'); + KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 8); + KUNIT_EXPECT_EQ(test, seq_buf_pop(&s), 'd'); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 7); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hellabc"); + + /* + * An overflowed buffer has nothing to pop, and stays overflowed. Use + * a buffer allocated at its exact size, so that KASAN reports any + * read past its end. + */ + buf = kunit_kmalloc(test, 16, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, buf); + seq_buf_init(&t, buf, 16); + KUNIT_EXPECT_EQ(test, seq_buf_printf(&t, "%s", "longer than sixteen"), -1); + KUNIT_EXPECT_EQ(test, seq_buf_pop(&t), -1); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&t)); +} + static void seq_buf_printf_test(struct kunit *test) { DECLARE_SEQ_BUF(s, 32); @@ -205,9 +319,9 @@ static void seq_buf_putmem_hex_overflow_test(struct kunit *test) DECLARE_SEQ_BUF(s, 20); const u8 data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; #ifdef __BIG_ENDIAN - const char *expected = "0001020304050607 "; + const char *expected = "0001020304050607 08"; #else - const char *expected = "0706050403020100 "; + const char *expected = "0706050403020100 09"; #endif KUNIT_EXPECT_EQ(test, seq_buf_putmem_hex(&s, data, sizeof(data)), -1); @@ -216,18 +330,437 @@ static void seq_buf_putmem_hex_overflow_test(struct kunit *test) KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), expected); } +static void seq_buf_puts_partial_overflow_test(struct kunit *test) +{ + static const char expected[] = "abcdefg"; + DECLARE_SEQ_BUF(s, 16); + struct seq_buf t; + char buf[8]; + + /* As much of the string as fits is written, like seq_buf_printf(). */ + seq_buf_puts(&s, "hello"); + KUNIT_EXPECT_EQ(test, seq_buf_puts(&s, " world, again"), -1); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 16); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hello world, ag"); + + /* Stale bytes after the data must not show up in the string. */ + memset(buf, 'X', sizeof(buf)); + seq_buf_init(&t, buf, sizeof(buf)); + seq_buf_putc(&t, 'a'); + KUNIT_EXPECT_EQ(test, seq_buf_puts(&t, "bcdefghij"), -1); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&t)); + + /* + * Check the buffer before seq_buf_str() does: it would write the + * terminator over the last byte itself, hiding whether the copy + * placed one there. The literal's own NUL is the eighth byte. + */ + KUNIT_EXPECT_MEMEQ(test, buf, expected, sizeof(buf)); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&t), "abcdefg"); +} + +static void seq_buf_putmem_partial_overflow_test(struct kunit *test) +{ + const u8 data[] = { 1, 2, 3, 4, 5, 6, 7 }; + const char expected[] = { 'a', 'b', 1, 2, 3, 4, 5, 6 }; + struct seq_buf s; + char buf[8]; + + memset(buf, 'X', sizeof(buf)); + seq_buf_init(&s, buf, sizeof(buf)); + seq_buf_putmem(&s, "ab", 2); + + /* One byte too many, so the last byte of @data is dropped. */ + KUNIT_EXPECT_EQ(test, seq_buf_putmem(&s, data, sizeof(data)), -1); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + + /* + * Check the buffer rather than seq_buf_str(): seq_buf_putmem() writes + * no NUL of its own, so a short copy leaves a stale byte at the end, + * exactly where seq_buf_str() would then write the terminator and + * hide it. + */ + KUNIT_EXPECT_MEMEQ(test, buf, expected, sizeof(buf)); +} + +static void seq_buf_putmem_hex_partial_overflow_test(struct kunit *test) +{ + const u8 data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; +#ifdef __BIG_ENDIAN + const char *expected = "0001020304050607 08"; + static const char expected_raw[] = "0001020304050607 080"; +#else + const char *expected = "0706050403020100 09"; + static const char expected_raw[] = "0706050403020100 090"; +#endif + struct seq_buf s; + char buf[20]; + + /* Stale bytes after the data must not show up in the string. */ + memset(buf, 'X', sizeof(buf)); + seq_buf_init(&s, buf, sizeof(buf)); + KUNIT_EXPECT_EQ(test, seq_buf_putmem_hex(&s, data, sizeof(data)), -1); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + + /* Before seq_buf_str() writes the terminator over the last byte. */ + KUNIT_EXPECT_MEMEQ(test, buf, expected_raw, sizeof(buf)); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), expected); +} + +/* + * Counters for the console that seq_buf_do_printk_test() registers while it + * runs. Only records carrying the marker are counted, so unrelated kernel + * messages do not disturb them. + * + * An empty record carries nothing to recognize it by, so count one only + * where the flaw puts it: directly after a record of ours, with nothing in + * between. That still misreads a bare line feed printed by another CPU in + * exactly that gap, but no longer counts one printed at any point while the + * console happens to be registered. + */ +#define SEQ_BUF_PRINTK_MARKER "sbdpkx" + +static unsigned int seq_buf_printk_marked; +static unsigned int seq_buf_printk_empty; +static bool seq_buf_printk_last_was_ours; + +static void seq_buf_printk_capture(struct console *con, + const char *s, unsigned int count) +{ + const char *text = s; + const char *prefix; + + /* + * Skip what printk() puts in front of the message: a timestamp, + * and the caller id as well under CONFIG_PRINTK_CALLER, so strip + * every bracketed group rather than just the first. + */ + while (count && text[0] == '[') { + prefix = memchr(text, ']', count); + if (!prefix) + break; + count -= prefix + 1 - text; + text = prefix + 1; + if (count && text[0] == ' ') { + text++; + count--; + } + } + + if (strnstr(text, SEQ_BUF_PRINTK_MARKER, count)) { + seq_buf_printk_marked++; + seq_buf_printk_last_was_ours = true; + return; + } + + if (seq_buf_printk_last_was_ours && + (count == 0 || (count == 1 && text[0] == '\n'))) + seq_buf_printk_empty++; + + seq_buf_printk_last_was_ours = false; +} + +static void seq_buf_printk_run(struct console *capture, struct seq_buf *s) +{ + seq_buf_printk_marked = 0; + seq_buf_printk_empty = 0; + seq_buf_printk_last_was_ours = false; + + /* + * register_console() will not take an unmatched console without + * CON_ENABLED, and unregister_console() clears it, so set it on + * every run to keep the test repeatable. + */ + capture->flags = CON_ENABLED; + register_console(capture); + seq_buf_do_printk(s, KERN_INFO); + unregister_console(capture); +} + +static void seq_buf_do_printk_test(struct kunit *test) +{ + /* + * A registered console is a global object: printk() reaches it + * through the console list from any CPU, and the console code writes + * back into it, so keep it out of this function's stack frame the + * way every other console in the tree does. + */ + static struct console capture = { + .name = "sbufcap", + .write = seq_buf_printk_capture, + .index = -1, + }; + DECLARE_SEQ_BUF(s, 8); + DECLARE_SEQ_BUF(t, 16); + DECLARE_SEQ_BUF(u, 8); + + /* + * Fill the buffer exactly, so that the NUL takes the place of the + * last byte and the string ends with the line feed before it. + */ + seq_buf_puts(&s, SEQ_BUF_PRINTK_MARKER); + seq_buf_putc(&s, '\n'); + seq_buf_putc(&s, '!'); + KUNIT_ASSERT_FALSE(test, seq_buf_has_overflowed(&s)); + KUNIT_ASSERT_EQ(test, seq_buf_used(&s), 8); + KUNIT_ASSERT_EQ(test, strlen(seq_buf_str(&s)), 7); + + seq_buf_printk_run(&capture, &s); + + /* The one line that was written, and nothing after it. */ + KUNIT_EXPECT_EQ(test, seq_buf_printk_marked, 1); + KUNIT_EXPECT_EQ(test, seq_buf_printk_empty, 0); + + /* Check that lines without a trailing newline are shown. */ + seq_buf_puts(&t, SEQ_BUF_PRINTK_MARKER "\n" SEQ_BUF_PRINTK_MARKER); + KUNIT_ASSERT_FALSE(test, seq_buf_has_overflowed(&t)); + + seq_buf_printk_run(&capture, &t); + + KUNIT_EXPECT_EQ(test, seq_buf_printk_marked, 2); + KUNIT_EXPECT_EQ(test, seq_buf_printk_empty, 0); + + /* + * The buffer above was exactly full, where "len" equals the size. A + * buffer that actually overflowed reaches the same bug by the other + * route the old test had, with "len" one past the size. + */ + seq_buf_puts(&u, SEQ_BUF_PRINTK_MARKER "\n"); + KUNIT_EXPECT_EQ(test, seq_buf_puts(&u, "yy"), -1); + KUNIT_ASSERT_TRUE(test, seq_buf_has_overflowed(&u)); + KUNIT_ASSERT_EQ(test, u.len, u.size + 1); + + seq_buf_printk_run(&capture, &u); + + KUNIT_EXPECT_EQ(test, seq_buf_printk_marked, 1); + KUNIT_EXPECT_EQ(test, seq_buf_printk_empty, 0); +} + +/* Long enough that it cannot fit in the room the test leaves for it. */ +#define SEQ_BUF_TEST_PATH "/seq_buf_kunit_path_name" + +static void seq_buf_path_overflow_test(struct kunit *test) +{ + DECLARE_SEQ_BUF(s, 32); + const char *expected = "keep:xxxxxxxxxxxxxxxxxxxxxxx"; + struct file *file; + size_t len; + int i; + + /* + * The tests run before anything writable is mounted, so take the file + * whose path gets printed from shmem, which needs no mount of its own. + */ + file = shmem_file_setup(SEQ_BUF_TEST_PATH, 0, EMPTY_VMA_FLAGS); + if (IS_ERR(file)) + kunit_skip(test, "cannot create a file to print the path of"); + + /* Leave less room than the path needs, so d_path() cannot fit it. */ + seq_buf_puts(&s, "keep:"); + len = seq_buf_used(&s); + for (i = len; i < 28; i++) + seq_buf_putc(&s, 'x'); + + KUNIT_EXPECT_EQ(test, seq_buf_path(&s, &file->f_path, "\n"), -1); + fput(file); + + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + + /* + * d_path() keeps as much of the path as fits when it does not fit + * whole, and seq_buf_str() would hand out that fragment, as it ends + * the string at the last byte of an overflowed buffer. + */ + for (i = 28; i < 32; i++) + KUNIT_EXPECT_EQ_MSG(test, s.buffer[i], '\0', + "byte %d past the data is not cleared", i); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), expected); +} + +static void seq_buf_strlen_test(struct kunit *test) +{ + DECLARE_SEQ_BUF(s, 16); + + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 0); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), ""); + + seq_buf_puts(&s, "hello"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 5); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s))); + + seq_buf_printf(&s, " %s", "world"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 11); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s))); +} + +static void seq_buf_strlen_printf_overflow_test(struct kunit *test) +{ + DECLARE_SEQ_BUF(s, 16); + DECLARE_SEQ_BUF(t, 8); + + seq_buf_printf(&s, "%s", "1234567890abcdefghij"); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 16); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 15); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "1234567890abcde"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s))); + + /* Output one byte too long for the NUL. */ + seq_buf_printf(&t, "%s", "12345678"); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&t)); + KUNIT_EXPECT_EQ(test, seq_buf_used(&t), 8); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&t), 7); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&t), "1234567"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&t), strlen(seq_buf_str(&t))); +} + +static void seq_buf_strlen_full_test(struct kunit *test) +{ + DECLARE_SEQ_BUF(s, 4); + DECLARE_SEQ_BUF(t, 8); + char *buf; + size_t len; + + /* Filled exactly, with no room left for a NUL, but not overflowed. */ + seq_buf_putc(&s, 'a'); + seq_buf_putc(&s, 'b'); + seq_buf_putc(&s, 'c'); + seq_buf_putc(&s, 'd'); + KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 4); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 3); + /* seq_buf_strlen() terminates the buffer by itself. */ + KUNIT_EXPECT_EQ(test, s.buffer[3], '\0'); + KUNIT_EXPECT_EQ(test, strnlen(s.buffer, s.size), 3); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "abc"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s))); + + /* A printf into a full buffer writes nothing. */ + KUNIT_EXPECT_EQ(test, seq_buf_printf(&s, "%s", "x"), -1); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 3); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "abc"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s))); + + len = seq_buf_get_buf(&t, &buf); + KUNIT_ASSERT_EQ(test, len, 8); + memset(buf, 'z', len); + seq_buf_commit(&t, len); + KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(&t)); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&t), 7); + KUNIT_EXPECT_EQ(test, t.buffer[7], '\0'); + KUNIT_EXPECT_EQ(test, strnlen(t.buffer, t.size), 7); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&t), "zzzzzzz"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&t), strlen(seq_buf_str(&t))); +} + +static void seq_buf_strlen_puts_overflow_test(struct kunit *test) +{ + DECLARE_SEQ_BUF(s, 16); + + /* A puts that does not fit copies as much as fits. */ + seq_buf_puts(&s, "hello"); + KUNIT_EXPECT_EQ(test, seq_buf_puts(&s, " this does not fit"), -1); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 15); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hello this does"); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s))); +} + +static void seq_buf_strlen_embedded_nul_test(struct kunit *test) +{ + static const char data[] = "ab\0cd"; + DECLARE_SEQ_BUF(s, 16); + + /* + * seq_buf_strlen() reports where it put the terminator, not where + * the first NUL is, so data carrying a NUL of its own makes the two + * disagree. That is expected, and is what the documented caveat is + * about. + */ + seq_buf_putmem(&s, data, sizeof(data) - 1); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 5); + KUNIT_EXPECT_EQ(test, strlen(seq_buf_str(&s)), 2); +} + +static void seq_buf_strlen_zero_size_test(struct kunit *test) +{ + char buf[] = "untouched"; + struct seq_buf s; + + /* + * A zero-sized seq_buf has nowhere to put a terminator. Both + * accessors report an empty string and leave the buffer alone + * rather than writing outside it. + */ + seq_buf_init(&s, buf, 0); + KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 0); + KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), ""); + KUNIT_EXPECT_STREQ(test, buf, "untouched"); +} + +static void seq_buf_terminate_test(struct kunit *test) +{ + char buf[16]; + struct seq_buf s; + + /* + * seq_buf_putmem() writes no NUL, so only seq_buf_terminate() can + * end these strings early. The NUL in the last byte of buf keeps + * the comparisons in bounds if it does not. + */ + + /* Terminates directly after the data when there is room. */ + memset(buf, 'z', sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + seq_buf_init(&s, buf, sizeof(buf)); + seq_buf_putmem(&s, "ab", 2); + seq_buf_terminate(&s); + KUNIT_EXPECT_STREQ(test, buf, "ab"); + + /* Terminates in the last byte once the buffer has overflowed. */ + memset(buf, 'z', sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + seq_buf_init(&s, buf, 4); + seq_buf_putmem(&s, "abcdef", 6); + KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s)); + seq_buf_terminate(&s); + KUNIT_EXPECT_STREQ(test, buf, "abc"); + + /* A zero-sized seq_buf is left alone. */ + strscpy(buf, "untouched", sizeof(buf)); + seq_buf_init(&s, buf, 0); + seq_buf_terminate(&s); + KUNIT_EXPECT_STREQ(test, buf, "untouched"); +} + static struct kunit_case seq_buf_test_cases[] = { KUNIT_CASE(seq_buf_init_test), + KUNIT_CASE(seq_buf_init_append_test), KUNIT_CASE(seq_buf_declare_test), KUNIT_CASE(seq_buf_clear_test), KUNIT_CASE(seq_buf_puts_test), KUNIT_CASE(seq_buf_puts_overflow_test), KUNIT_CASE(seq_buf_putc_test), + KUNIT_CASE(seq_buf_pop_test), KUNIT_CASE(seq_buf_printf_test), KUNIT_CASE(seq_buf_printf_overflow_test), KUNIT_CASE(seq_buf_get_buf_commit_test), KUNIT_CASE(seq_buf_putmem_hex_test), KUNIT_CASE(seq_buf_putmem_hex_overflow_test), + KUNIT_CASE(seq_buf_puts_partial_overflow_test), + KUNIT_CASE(seq_buf_putmem_partial_overflow_test), + KUNIT_CASE(seq_buf_putmem_hex_partial_overflow_test), + KUNIT_CASE(seq_buf_path_overflow_test), + KUNIT_CASE(seq_buf_strlen_test), + KUNIT_CASE(seq_buf_strlen_printf_overflow_test), + KUNIT_CASE(seq_buf_strlen_full_test), + KUNIT_CASE(seq_buf_strlen_puts_overflow_test), + KUNIT_CASE(seq_buf_strlen_embedded_nul_test), + KUNIT_CASE(seq_buf_strlen_zero_size_test), + KUNIT_CASE(seq_buf_terminate_test), + KUNIT_CASE(seq_buf_do_printk_test), {} }; diff --git a/lib/tests/stackinit_kunit.c b/lib/tests/stackinit_kunit.c index ff2784769772..d589536afa20 100644 --- a/lib/tests/stackinit_kunit.c +++ b/lib/tests/stackinit_kunit.c @@ -369,17 +369,24 @@ union test_small_end { char four; }; -#define ALWAYS_PASS WANT_SUCCESS -#define ALWAYS_FAIL XFAIL +#define ALWAYS_PASS WANT_SUCCESS +#define ALWAYS_FAIL XFAIL #ifdef CONFIG_INIT_STACK_NONE -# define USER_PASS XFAIL -# define BYREF_PASS XFAIL -# define STRONG_PASS XFAIL +# define INSTRUMENTED_PASS XFAIL #else -# define USER_PASS WANT_SUCCESS -# define BYREF_PASS WANT_SUCCESS -# define STRONG_PASS WANT_SUCCESS +# define INSTRUMENTED_PASS WANT_SUCCESS +#endif + +/* + * Clang 24 and later emit initialization on the jumps that bypass the + * declaration. GCC still initializes only at the declaration. Track + * test expectation based on CONFIG_INIT_STACK_NONE above. + */ +#if defined(CONFIG_CC_IS_CLANG) && CONFIG_CLANG_VERSION >= 240000 +# define BYPASS_PASS INSTRUMENTED_PASS +#else +# define BYPASS_PASS XFAIL #endif #define DEFINE_SCALAR_TEST(name, init, xfail) \ @@ -432,23 +439,23 @@ DEFINE_STRUCT_TESTS(old_zero, ALWAYS_PASS); DEFINE_UNION_TESTS(zero, ALWAYS_PASS); DEFINE_UNION_TESTS(old_zero, ALWAYS_PASS); /* Struct initializers: padding may be left uninitialized. */ -DEFINE_STRUCT_INITIALIZER_TESTS(static, STRONG_PASS); -DEFINE_STRUCT_INITIALIZER_TESTS(dynamic, STRONG_PASS); -DEFINE_STRUCT_INITIALIZER_TESTS(runtime, STRONG_PASS); -DEFINE_STRUCT_INITIALIZER_TESTS(assigned_static, STRONG_PASS); -DEFINE_STRUCT_INITIALIZER_TESTS(assigned_dynamic, STRONG_PASS); +DEFINE_STRUCT_INITIALIZER_TESTS(static, INSTRUMENTED_PASS); +DEFINE_STRUCT_INITIALIZER_TESTS(dynamic, INSTRUMENTED_PASS); +DEFINE_STRUCT_INITIALIZER_TESTS(runtime, INSTRUMENTED_PASS); +DEFINE_STRUCT_INITIALIZER_TESTS(assigned_static, INSTRUMENTED_PASS); +DEFINE_STRUCT_INITIALIZER_TESTS(assigned_dynamic, INSTRUMENTED_PASS); DEFINE_STRUCT_TESTS(assigned_copy, ALWAYS_FAIL); -DEFINE_UNION_INITIALIZER_TESTS(static, STRONG_PASS); -DEFINE_UNION_INITIALIZER_TESTS(dynamic, STRONG_PASS); -DEFINE_UNION_INITIALIZER_TESTS(runtime, STRONG_PASS); -DEFINE_UNION_INITIALIZER_TESTS(assigned_static, STRONG_PASS); -DEFINE_UNION_INITIALIZER_TESTS(assigned_dynamic, STRONG_PASS); +DEFINE_UNION_INITIALIZER_TESTS(static, INSTRUMENTED_PASS); +DEFINE_UNION_INITIALIZER_TESTS(dynamic, INSTRUMENTED_PASS); +DEFINE_UNION_INITIALIZER_TESTS(runtime, INSTRUMENTED_PASS); +DEFINE_UNION_INITIALIZER_TESTS(assigned_static, INSTRUMENTED_PASS); +DEFINE_UNION_INITIALIZER_TESTS(assigned_dynamic, INSTRUMENTED_PASS); DEFINE_UNION_TESTS(assigned_copy, ALWAYS_FAIL); /* No initialization without compiler instrumentation. */ -DEFINE_SCALAR_TESTS(none, STRONG_PASS); -DEFINE_STRUCT_TESTS(none, BYREF_PASS); +DEFINE_SCALAR_TESTS(none, INSTRUMENTED_PASS); +DEFINE_STRUCT_TESTS(none, INSTRUMENTED_PASS); /* Initialization of members with __user attribute. */ -DEFINE_TEST(user, struct test_user, STRUCT, none, USER_PASS); +DEFINE_TEST(user, struct test_user, STRUCT, none, INSTRUMENTED_PASS); /* * Check two uses through a variable declaration outside either path, @@ -493,6 +500,36 @@ static int noinline __leaf_switch_none(int path, bool fill) return 0; } +static noinline int __leaf_goto_none(bool fill) +{ + goto bypass; + /* + * This declaration is jumped over by the goto above, so it is + * never reached. Compilers that initialize only at the point of + * declaration leave this variable untouched. + */ + uint64_t var[10]; + +bypass: + target_start = &var; + target_size = sizeof(var); + if (fill) { + fill_start = &var; + fill_size = sizeof(var); + + memset(fill_start, (forced_mask | 0x55) & FILL_BYTE, fill_size); + } + memcpy(check_buf, target_start, target_size); + + return 0; +} + +static noinline int leaf_goto_none(unsigned long sp, bool fill, + uint64_t *arg) +{ + return __leaf_goto_none(fill); +} + static noinline int leaf_switch_1_none(unsigned long sp, bool fill, uint64_t *arg) { @@ -506,13 +543,14 @@ static noinline int leaf_switch_2_none(unsigned long sp, bool fill, } /* - * These are expected to fail for most configurations because neither - * GCC nor Clang have a way to perform initialization of variables in - * non-code areas (i.e. in a switch statement before the first "case"). - * https://llvm.org/pr44916 + * A declaration whose definition point is jumped over, either by a switch + * dispatch reaching a "case" label below it or by a goto, used to be left + * uninitialized: the initialization is emitted where the variable is + * declared, and that point is never reached. */ -DEFINE_TEST_DRIVER(switch_1_none, uint64_t, SCALAR, ALWAYS_FAIL); -DEFINE_TEST_DRIVER(switch_2_none, uint64_t, SCALAR, ALWAYS_FAIL); +DEFINE_TEST_DRIVER(switch_1_none, uint64_t, SCALAR, BYPASS_PASS); +DEFINE_TEST_DRIVER(switch_2_none, uint64_t, SCALAR, BYPASS_PASS); +DEFINE_TEST_DRIVER(goto_none, uint64_t, SCALAR, BYPASS_PASS); #define KUNIT_test_scalars(init) \ KUNIT_CASE(test_u8_ ## init), \ @@ -568,6 +606,7 @@ static struct kunit_case stackinit_test_cases[] = { KUNIT_test_scalars(none), KUNIT_CASE(test_switch_1_none), KUNIT_CASE(test_switch_2_none), + KUNIT_CASE(test_goto_none), /* STRUCTLEAK_BYREF should cover from here down. */ KUNIT_test_structs(none), /* STRUCTLEAK will only cover this. */ diff --git a/scripts/coccinelle/api/kmalloc_objs.cocci b/scripts/coccinelle/api/kmalloc_objs.cocci index e9a415b7b6f4..0a98ddf03eca 100644 --- a/scripts/coccinelle/api/kmalloc_objs.cocci +++ b/scripts/coccinelle/api/kmalloc_objs.cocci @@ -24,27 +24,57 @@ def alloc_array(name): print(f"Unknown transform for {name}", file=sys.stderr) return func -// This excludes anything that is assigning to or from integral types or -// string literals. Everything else gets the sizeof() extracted for the -// kmalloc_obj() type/var argument. sizeof(void *) is also excluded because -// it will need case-by-case double-checking to make sure the right type is +// Allocations sized by a byte-sized type (BYTE_TYPES) or a string literal, +// and allocations assigned to a pointer to a byte-sized type, are byte +// buffers and are left alone. sizeof(void *) is also excluded because it +// will need case-by-case double-checking to make sure the right type is // being assigned. +// +// Allocations sized by a multi-byte integral type (MULTIBYTE_TYPES) are +// converted when they are assigned to a pointer to that same type. +// Otherwise they are left alone: the target may be a pointer to an array +// of that type, such as "s16 (*pairs)[2]", for which the converted +// allocation would have the wrong pointer type. For the same reason, +// arrays of pointers to integral types sized as sizeof(char *) and the +// like are left alone. Allocations of other types assigned to a pointer to +// a multi-byte integral type are left alone too. +// +// Everything else gets the sizeof() extracted for the kmalloc_obj() +// type/var argument. +// +// The first matching alternative below wins, so the exclusions must come +// before the more general conversions. @direct depends on patch && !(file in "tools") && !(file in "samples")@ typedef u8, u16, u32, u64; typedef __u8, __u16, __u32, __u64; typedef uint8_t, uint16_t, uint32_t, uint64_t; +typedef s8, s16, s32, s64; +typedef __s8, __s16, __s32, __s64; +typedef int8_t, int16_t, int32_t, int64_t; typedef uchar, ushort, uint, ulong; typedef __le16, __le32, __le64; typedef __be16, __be32, __be64; typedef wchar_t; -type INTEGRAL = {u8,__u8,uint8_t,char,unsigned char,uchar,wchar_t, - u16,__u16,uint16_t,unsigned short,ushort, - u32,__u32,uint32_t,unsigned int,uint, - u64,__u64,uint64_t,unsigned long,ulong, - __le16,__le32,__le64,__be16,__be32,__be64}; +type BYTE_TYPES = {char,signed char,unsigned char,uchar, + u8,__u8,uint8_t,s8,__s8,int8_t}; +type MULTIBYTE_TYPES = {short,short int,signed short,signed short int, + unsigned short,unsigned short int,ushort, + int,signed,signed int,unsigned,unsigned int,uint, + long,long int,signed long,signed long int, + unsigned long,unsigned long int,ulong, + long long,long long int, + signed long long,signed long long int, + unsigned long long,unsigned long long int, + u16,__u16,uint16_t,s16,__s16,int16_t, + u32,__u32,uint32_t,s32,__s32,int32_t, + u64,__u64,uint64_t,s64,__s64,int64_t, + __le16,__le32,__le64,__be16,__be32,__be64, + wchar_t}; char [] STRING; -INTEGRAL *BYTES; -INTEGRAL **BYTES_PTRS; +BYTE_TYPES *BYTES; +MULTIBYTE_TYPES *MULTIBYTES; +const MULTIBYTE_TYPES *CONST_MULTIBYTES; +MULTIBYTE_TYPES MULTIBYTE; type TYPE; expression VAR; expression GFP; @@ -59,66 +89,133 @@ fresh identifier ALLOC_OBJS = script:python(ALLOC_ARRAY) { alloc_array(ALLOC_ARR @@ ( +// Convert a single object sized by its target: p = kmalloc(sizeof(*p), gfp) - VAR = ALLOC((sizeof(*VAR)), GFP) + VAR = ALLOC_OBJ(*VAR, GFP) | - ALLOC((\(sizeof(STRING)\|sizeof(INTEGRAL)\|sizeof(INTEGRAL *)\)), GFP) +// Exclude byte buffers and integral pointers: kmalloc(sizeof(u8), gfp), +// kmalloc(sizeof("str"), gfp), kmalloc(sizeof(char *), gfp) + ALLOC((\(sizeof(STRING)\|sizeof(BYTE_TYPES)\| + sizeof(BYTE_TYPES *)\|sizeof(MULTIBYTE_TYPES *)\)), GFP) | - BYTES = ALLOC((sizeof(E)), GFP) +// Exclude anything assigned to a byte pointer: +// u8 *buf = kmalloc(sizeof(*hdr), gfp) + BYTES = ALLOC((\(sizeof(E)\|sizeof(TYPE)\)), GFP) | - BYTES = ALLOC((sizeof(TYPE)), GFP) +// Convert a multi-byte type to a pointer to it: +// u32 *p = kmalloc(sizeof(u32), gfp) + \(MULTIBYTES\|CONST_MULTIBYTES\) = +- ALLOC((sizeof(MULTIBYTE_TYPES)), GFP) ++ ALLOC_OBJ(MULTIBYTE_TYPES, GFP) | - BYTES_PTRS = ALLOC((sizeof(E)), GFP) +// Same by expression: u32 *p = kmalloc(sizeof(p[0]), gfp) + \(MULTIBYTES\|CONST_MULTIBYTES\) = +- ALLOC((sizeof(MULTIBYTE)), GFP) ++ ALLOC_OBJ(MULTIBYTE, GFP) | - BYTES_PTRS = ALLOC((sizeof(TYPE)), GFP) +// Exclude other multi-byte sizes, e.g. to pointers to arrays: s16 (*p)[2] = ... + ALLOC((\(sizeof(MULTIBYTE_TYPES)\|sizeof(MULTIBYTE)\)), GFP) | +// Exclude anything else assigned to a multi-byte pointer: +// u32 *p = kmalloc(sizeof(*hdr), gfp) + \(MULTIBYTES\|CONST_MULTIBYTES\) = ALLOC((\(sizeof(E)\|sizeof(TYPE)\)), GFP) +| +// Exclude void pointers, to be checked by hand: kmalloc(sizeof(void *), gfp) ALLOC((sizeof(void *)), GFP) | +// Convert any other expression: p = kmalloc(sizeof(s->item), gfp) - ALLOC((sizeof(E)), GFP) + ALLOC_OBJ(E, GFP) | +// Convert any other type: p = kmalloc(sizeof(struct item), gfp) - ALLOC((sizeof(TYPE)), GFP) + ALLOC_OBJ(TYPE, GFP) | - ALLOC_ARRAY(COUNT, (\(sizeof(STRING)\|sizeof(INTEGRAL)\|sizeof(INTEGRAL *)\)), GFP) -| - BYTES = ALLOC_ARRAY(COUNT, (sizeof(E)), GFP) -| - BYTES = ALLOC_ARRAY(COUNT, (sizeof(TYPE)), GFP) -| - BYTES_PTRS = ALLOC_ARRAY(COUNT, (sizeof(E)), GFP) +// The same, for arrays allocated as (count, size): +// Exclude byte buffers and integral pointers: kcalloc(n, sizeof(u8), gfp), +// kcalloc(n, sizeof(char *), gfp) + ALLOC_ARRAY(COUNT, (\(sizeof(STRING)\|sizeof(BYTE_TYPES)\| + sizeof(BYTE_TYPES *)\|sizeof(MULTIBYTE_TYPES *)\)), GFP) | - BYTES_PTRS = ALLOC_ARRAY(COUNT, (sizeof(TYPE)), GFP) +// Exclude arrays assigned to a byte pointer: +// u8 *buf = kcalloc(n, sizeof(*hdr), gfp) + BYTES = ALLOC_ARRAY(COUNT, (\(sizeof(E)\|sizeof(TYPE)\)), GFP) | - ALLOC_ARRAY((\(sizeof(STRING)\|sizeof(INTEGRAL)\|sizeof(INTEGRAL *)\)), COUNT, GFP) +// Convert a multi-byte array to a pointer to it: +// u32 *p = kcalloc(n, sizeof(u32), gfp) + \(MULTIBYTES\|CONST_MULTIBYTES\) = +- ALLOC_ARRAY(COUNT, (sizeof(MULTIBYTE_TYPES)), GFP) ++ ALLOC_OBJS(MULTIBYTE_TYPES, COUNT, GFP) | - BYTES = ALLOC_ARRAY((sizeof(E)), COUNT, GFP) +// Same by expression: u32 *p = kcalloc(n, sizeof(*p), gfp) + \(MULTIBYTES\|CONST_MULTIBYTES\) = +- ALLOC_ARRAY(COUNT, (sizeof(MULTIBYTE)), GFP) ++ ALLOC_OBJS(MULTIBYTE, COUNT, GFP) | - BYTES = ALLOC_ARRAY((sizeof(TYPE)), COUNT, GFP) +// Exclude other multi-byte arrays: +// s16 (*pairs)[2] = kcalloc(n, sizeof(s16), gfp) + ALLOC_ARRAY(COUNT, (\(sizeof(MULTIBYTE_TYPES)\|sizeof(MULTIBYTE)\)), GFP) | - BYTES_PTRS = ALLOC_ARRAY((sizeof(E)), COUNT, GFP) -| - BYTES_PTRS = ALLOC_ARRAY((sizeof(TYPE)), COUNT, GFP) +// Exclude other arrays assigned to a multi-byte pointer: +// u32 *p = kcalloc(n, sizeof(*hdr), gfp) + \(MULTIBYTES\|CONST_MULTIBYTES\) = ALLOC_ARRAY(COUNT, (\(sizeof(E)\|sizeof(TYPE)\)), GFP) | +// Exclude arrays of void pointers: kcalloc(n, sizeof(void *), gfp) ALLOC_ARRAY(COUNT, (sizeof(void *)), GFP) | - ALLOC_ARRAY((sizeof(void *)), COUNT, GFP) -| +// Convert any other expression: p = kcalloc(n, sizeof(*p), gfp) - ALLOC_ARRAY(COUNT, (sizeof(E)), GFP) + ALLOC_OBJS(E, COUNT, GFP) | +// Convert any other type: p = kcalloc(n, sizeof(struct item), gfp) - ALLOC_ARRAY(COUNT, (sizeof(TYPE)), GFP) + ALLOC_OBJS(TYPE, COUNT, GFP) | +// The same, for arrays allocated as (size, count): +// Exclude byte buffers and integral pointers: kcalloc(sizeof(u8), n, gfp), +// kcalloc(sizeof(char *), n, gfp) + ALLOC_ARRAY((\(sizeof(STRING)\|sizeof(BYTE_TYPES)\| + sizeof(BYTE_TYPES *)\|sizeof(MULTIBYTE_TYPES *)\)), COUNT, GFP) +| +// Exclude arrays assigned to a byte pointer: +// u8 *buf = kcalloc(sizeof(*hdr), n, gfp) + BYTES = ALLOC_ARRAY((\(sizeof(E)\|sizeof(TYPE)\)), COUNT, GFP) +| +// Convert a multi-byte array to a pointer to it: +// u32 *p = kcalloc(sizeof(u32), n, gfp) + \(MULTIBYTES\|CONST_MULTIBYTES\) = +- ALLOC_ARRAY((sizeof(MULTIBYTE_TYPES)), COUNT, GFP) ++ ALLOC_OBJS(MULTIBYTE_TYPES, COUNT, GFP) +| +// Same by expression: u32 *p = kcalloc(sizeof(*p), n, gfp) + \(MULTIBYTES\|CONST_MULTIBYTES\) = +- ALLOC_ARRAY((sizeof(MULTIBYTE)), COUNT, GFP) ++ ALLOC_OBJS(MULTIBYTE, COUNT, GFP) +| +// Exclude other multi-byte arrays: +// s16 (*pairs)[2] = kcalloc(sizeof(s16), n, gfp) + ALLOC_ARRAY((\(sizeof(MULTIBYTE_TYPES)\|sizeof(MULTIBYTE)\)), COUNT, GFP) +| +// Exclude other arrays assigned to a multi-byte pointer: +// u32 *p = kcalloc(sizeof(*hdr), n, gfp) + \(MULTIBYTES\|CONST_MULTIBYTES\) = ALLOC_ARRAY((\(sizeof(E)\|sizeof(TYPE)\)), COUNT, GFP) +| +// Exclude arrays of void pointers: kcalloc(sizeof(void *), n, gfp) + ALLOC_ARRAY((sizeof(void *)), COUNT, GFP) +| +// Convert any other expression: p = kcalloc(sizeof(*p), n, gfp) - ALLOC_ARRAY((sizeof(E)), COUNT, GFP) + ALLOC_OBJS(E, COUNT, GFP) | +// Convert any other type: p = kcalloc(sizeof(struct item), n, gfp) - ALLOC_ARRAY((sizeof(TYPE)), COUNT, GFP) + ALLOC_OBJS(TYPE, COUNT, GFP) | +// Convert flexible array structures: p = kmalloc(struct_size(p, data, n), gfp) - ALLOC(struct_size(VAR, FLEX, COUNT), GFP) + ALLOC_FLEX(*VAR, FLEX, COUNT, GFP) | +// Same by type: kmalloc(struct_size_t(struct item, data, n), gfp) - ALLOC(struct_size_t(TYPE, FLEX, COUNT), GFP) + ALLOC_FLEX(TYPE, FLEX, COUNT, GFP) ) diff --git a/scripts/gcc-plugins/randomize_layout_plugin.c b/scripts/gcc-plugins/randomize_layout_plugin.c index ff65a4f87f24..1e66f45fe29b 100644 --- a/scripts/gcc-plugins/randomize_layout_plugin.c +++ b/scripts/gcc-plugins/randomize_layout_plugin.c @@ -22,7 +22,7 @@ #define ORIG_TYPE_NAME(node) \ (TYPE_NAME(TYPE_MAIN_VARIANT(node)) != NULL_TREE ? ((const unsigned char *)IDENTIFIER_POINTER(TYPE_NAME(TYPE_MAIN_VARIANT(node)))) : (const unsigned char *)"anonymous") -#define INFORM(loc, msg, ...) inform(loc, "randstruct: " msg, ##__VA_ARGS__) +#define INFORM(loc, msg, ...) warning_at(loc, 0, "randstruct: " msg, ##__VA_ARGS__) #define MISMATCH(loc, how, ...) INFORM(loc, "casting between randomized structure pointer types (" how "): %qT and %qT\n", __VA_ARGS__) __visible int plugin_is_GPL_compatible; @@ -699,6 +699,63 @@ static void handle_local_var_initializers(void) } /* + * Does @container reach a field of type @member_type by a chain of + * by-value members? That is the relationship container_of() expresses -- + * its @member argument may be a dotted path, e.g. + * container_of(inode, struct ceph_inode_info, netfs.inode) -- so a cast + * from @member_type * to @container * is legitimate rather than a + * layout-confusing one. + * + * container_of() used to leave a "void *__mptr" temporary behind, and this + * pass recognised such casts by that name. Commit f9e7a7564834 + * ("container_of: remove local __mptr variable") removed it to stop nested + * container_of() shadowing itself, and the cast now folds to a bare SSA + * copy when the member sits at offset 0, leaving nothing syntactic to key + * on. Match the type relationship instead. + * + * The depth bound keeps this cheap; container_of() paths are one or two + * members deep in practice. + */ +#define CONTAINER_OF_MAX_DEPTH 4 + +static bool is_container_of_cast(const_tree container, const_tree member_type, + int depth) +{ + const_tree field; + + if (container == NULL_TREE || depth > CONTAINER_OF_MAX_DEPTH) + return false; + + if (TREE_CODE(container) != RECORD_TYPE && + TREE_CODE(container) != UNION_TYPE) + return false; + + for (field = TYPE_FIELDS(container); field; field = DECL_CHAIN(field)) { + const_tree field_type; + + if (TREE_CODE(field) != FIELD_DECL) + continue; + + /* + * Only a member at offset 0 can reach here: for any other + * offset container_of()'s subtraction survives folding, the + * cast's rhs stays void *, and the caller skipped it above. + */ + if (!integer_zerop(byte_position(field))) + continue; + + field_type = TYPE_MAIN_VARIANT(TREE_TYPE(field)); + if (field_type == member_type) + return true; + + if (is_container_of_cast(field_type, member_type, depth + 1)) + return true; + } + + return false; +} + +/* * iterate over all statements to find "bad" casts: * those where the address of the start of a structure is cast * to a pointer of a structure of a different type, or a @@ -799,10 +856,8 @@ static unsigned int find_bad_casts_execute(void) #endif MISMATCH(gimple_location(stmt), "op0", ptr_lhs_type, op0_type); } else { - const_tree ssa_name_var = SSA_NAME_VAR(rhs1); /* skip bogus type casts introduced by container_of */ - if (ssa_name_var != NULL_TREE && DECL_NAME(ssa_name_var) && - !strcmp((const char *)DECL_NAME_POINTER(ssa_name_var), "__mptr")) + if (is_container_of_cast(ptr_lhs_type, ptr_rhs_type, 0)) continue; #ifndef __DEBUG_PLUGIN if (lookup_attribute("randomize_performed", TYPE_ATTRIBUTES(ptr_rhs_type))) diff --git a/tools/testing/nvdimm/test/ndtest.c b/tools/testing/nvdimm/test/ndtest.c index 2051ad5d4882..f097f2992966 100644 --- a/tools/testing/nvdimm/test/ndtest.c +++ b/tools/testing/nvdimm/test/ndtest.c @@ -693,7 +693,7 @@ static ssize_t flags_show(struct device *dev, if (seq_buf_used(&s)) seq_buf_printf(&s, "\n"); - return seq_buf_used(&s); + return seq_buf_strlen(&s); } static DEVICE_ATTR_RO(flags); |
