From 6b828a87a63a29bc6b4a597e3a920e2b19c8274e Mon Sep 17 00:00:00 2001 From: Alejandro Colomar Date: Tue, 22 Sep 2026 21:34:09 +0200 Subject: [PATCH 1/4] scripts/coccinelle: Add script for using ARRAY_END() Recently, we added an ARRAY_END() macro to simplify finding a pointer one past the last element in an array. Such a pointer is often called the 'end', and thus the macro name. Make it easy to find more places where that macro should be used. See also: 436debc9cad8 ("array_size.h: add ARRAY_END()") 8118f197b7b7 ("mm: fix benign off-by-one bugs") a9e5620c9a9e ("kernel: fix off-by-one benign bugs") 61e9210e2392 ("mm: use ARRAY_END() instead of open-coding it") Cc: Kees Cook Cc: Markus Elfring Cc: Julia Lawall Cc: Nicolas Palix Signed-off-by: Alejandro Colomar Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/array_end.cocci | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 scripts/coccinelle/misc/array_end.cocci diff --git a/scripts/coccinelle/misc/array_end.cocci b/scripts/coccinelle/misc/array_end.cocci new file mode 100644 index 000000000000..88c664de974a --- /dev/null +++ b/scripts/coccinelle/misc/array_end.cocci @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Use ARRAY_END instead of an expression derived from ARRAY_SIZE +// +// Confidence: High +// Copyright: 2026, Alejandro Colomar +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@depends on context@ +type T; +T[] a; +expression b; +@@ +( +* (a + ARRAY_SIZE(a)) +| +* (&a[0] + ARRAY_SIZE(a)) +| +* (&a[ARRAY_SIZE(a)]) +| +* (&a[ARRAY_SIZE(a) - b]) +) + +@depends on patch@ +type T; +T[] a; +expression b; +@@ +( +- (a + ARRAY_SIZE(a)) ++ ARRAY_END(a) +| +- (&a[0] + ARRAY_SIZE(a)) ++ ARRAY_END(a) +| +- (&a[ARRAY_SIZE(a)]) ++ ARRAY_END(a) +| +- (&a[ARRAY_SIZE(a) - b]) ++ ARRAY_END(a) - b +) + +@r depends on org || report@ +type T; +T[] a; +expression b; +position p; +@@ +( + (a@p + ARRAY_SIZE(a)) +| + (&a[0]@p + ARRAY_SIZE(a)) +| + (&a[ARRAY_SIZE(a)]@p) +| + (&a[ARRAY_SIZE(a)@p - b]) +) + +@script:python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING should use ARRAY_END") + +@script:python depends on report@ +p << r.p; +@@ + +coccilib.report.print_report(p[0], "WARNING: opportunity for ARRAY_END") From 14be5faa8be7cebb559c91a41d48545c0b1faa16 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 27 Sep 2026 11:16:22 +0200 Subject: [PATCH 2/4] scripts/coccinelle: improve efficiency of array_end.cocci * Add disable zero_multiple_format to each rule to avoid considering the possibility that an array index is expressed using '\0' * Omit outer parentheses on context and org/report rules that only match ubt don't transform, since matching a subterm gives the same result whether or not the parentheses are present. Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/array_end.cocci | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/coccinelle/misc/array_end.cocci b/scripts/coccinelle/misc/array_end.cocci index 88c664de974a..2f0bbc4ce17d 100644 --- a/scripts/coccinelle/misc/array_end.cocci +++ b/scripts/coccinelle/misc/array_end.cocci @@ -10,22 +10,22 @@ virtual context virtual org virtual report -@depends on context@ +@depends on context disable zero_multiple_format@ type T; T[] a; expression b; @@ ( -* (a + ARRAY_SIZE(a)) +* a + ARRAY_SIZE(a) | -* (&a[0] + ARRAY_SIZE(a)) +* &a[0] + ARRAY_SIZE(a) | -* (&a[ARRAY_SIZE(a)]) +* &a[ARRAY_SIZE(a)] | -* (&a[ARRAY_SIZE(a) - b]) +* &a[ARRAY_SIZE(a) - b] ) -@depends on patch@ +@depends on patch disable zero_multiple_format@ type T; T[] a; expression b; @@ -44,20 +44,20 @@ expression b; + ARRAY_END(a) - b ) -@r depends on org || report@ +@r depends on org || report disable zero_multiple_format@ type T; T[] a; expression b; position p; @@ ( - (a@p + ARRAY_SIZE(a)) + a@p + ARRAY_SIZE(a) | - (&a[0]@p + ARRAY_SIZE(a)) + &a[0]@p + ARRAY_SIZE(a) | - (&a[ARRAY_SIZE(a)]@p) + &a[ARRAY_SIZE(a)]@p | - (&a[ARRAY_SIZE(a)@p - b]) + &a[ARRAY_SIZE(a)@p - b] ) @script:python depends on org@ From afb17ae1dbf7464cacb8b8e2f8efaf8f7dc7b1b3 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Sun, 1 Nov 2026 02:35:46 -0800 Subject: [PATCH 3/4] coccinelle: Add devm_kmalloc_objs conversion script Like commit bdc5071d7f7b ("coccinelle: Add kmalloc_objs conversion script") with commit 1c7bbaeed110 ("coccinelle: kmalloc_obj: Remove default GFP_KERNEL arg") Finds and converts sized devm_kmalloc-family of allocations into the typed devm_kmalloc_obj-family of allocations. Signed-off-by: Joe Perches Signed-off-by: Julia Lawall --- .../coccinelle/api/devm_kmalloc_objs.cocci | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 scripts/coccinelle/api/devm_kmalloc_objs.cocci diff --git a/scripts/coccinelle/api/devm_kmalloc_objs.cocci b/scripts/coccinelle/api/devm_kmalloc_objs.cocci new file mode 100644 index 000000000000..322f79a2d965 --- /dev/null +++ b/scripts/coccinelle/api/devm_kmalloc_objs.cocci @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Use devm_kmalloc_obj family of macros for allocations +/// +// Confidence: High +// Options: --include-headers-for-types --all-includes --include-headers --keep-comments + +virtual patch + +@initialize:python@ +@@ +import sys + +def alloc_array(name): + func = "FAILED_RENAME" + if name == "devm_kmalloc_array": + func = "devm_kmalloc_objs" + elif name == "devm_kvmalloc_array": + func = "devm_kvmalloc_objs" + elif name == "devm_kcalloc": + func = "devm_kzalloc_objs" + elif name == "devm_kvcalloc": + func = "devm_kvzalloc_objs" + else: + 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 +// being assigned. +@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 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}; +char [] STRING; +INTEGRAL *BYTES; +INTEGRAL **BYTES_PTRS; +type TYPE; +expression VAR; +expression GFP; +expression COUNT; +expression FLEX; +expression E; +expression DEV; +identifier ALLOC =~ "^devm_kv?[mz]alloc$"; +fresh identifier ALLOC_OBJ = ALLOC ## "_obj"; +fresh identifier ALLOC_FLEX = ALLOC ## "_flex"; +identifier ALLOC_ARRAY = {devm_kmalloc_array,devm_kvmalloc_array,devm_kcalloc,devm_kvcalloc}; +fresh identifier ALLOC_OBJS = script:python(ALLOC_ARRAY) { alloc_array(ALLOC_ARRAY) }; +@@ + +( +- VAR = ALLOC(DEV,(sizeof(*VAR)), GFP) ++ VAR = ALLOC_OBJ(DEV,*VAR, GFP) +| + ALLOC(DEV,(\(sizeof(STRING)\|sizeof(INTEGRAL)\|sizeof(INTEGRAL *)\)), GFP) +| + BYTES = ALLOC(DEV,(sizeof(E)), GFP) +| + BYTES = ALLOC(DEV,(sizeof(TYPE)), GFP) +| + BYTES_PTRS = ALLOC(DEV,(sizeof(E)), GFP) +| + BYTES_PTRS = ALLOC(DEV,(sizeof(TYPE)), GFP) +| + ALLOC(DEV,(sizeof(void *)), GFP) +| +- ALLOC(DEV,(sizeof(E)), GFP) ++ ALLOC_OBJ(DEV, E, GFP) +| +- ALLOC(DEV,(sizeof(TYPE)), GFP) ++ ALLOC_OBJ(DEV,TYPE, GFP) +| + ALLOC_ARRAY(DEV,COUNT, (\(sizeof(STRING)\|sizeof(INTEGRAL)\|sizeof(INTEGRAL *)\)), GFP) +| + BYTES = ALLOC_ARRAY(DEV, COUNT, (sizeof(E)), GFP) +| + BYTES = ALLOC_ARRAY(DEV,COUNT, (sizeof(TYPE)), GFP) +| + BYTES_PTRS = ALLOC_ARRAY(DEV, COUNT, (sizeof(E)), GFP) +| + BYTES_PTRS = ALLOC_ARRAY(DEV, COUNT, (sizeof(TYPE)), GFP) +| + ALLOC_ARRAY(DEV, (\(sizeof(STRING)\|sizeof(INTEGRAL)\|sizeof(INTEGRAL *)\)), COUNT, GFP) +| + BYTES = ALLOC_ARRAY(DEV, (sizeof(E)), COUNT, GFP) +| + BYTES = ALLOC_ARRAY(DEV, (sizeof(TYPE)), COUNT, GFP) +| + BYTES_PTRS = ALLOC_ARRAY(DEV, (sizeof(E)), COUNT, GFP) +| + BYTES_PTRS = ALLOC_ARRAY(DEV, (sizeof(TYPE)), COUNT, GFP) +| + ALLOC_ARRAY(DEV, COUNT, (sizeof(void *)), GFP) +| + ALLOC_ARRAY(DEV, (sizeof(void *)), COUNT, GFP) +| +- ALLOC_ARRAY(DEV, COUNT, (sizeof(E)), GFP) ++ ALLOC_OBJS(DEV, E, COUNT, GFP) +| +- ALLOC_ARRAY(DEV, COUNT, (sizeof(TYPE)), GFP) ++ ALLOC_OBJS(DEV, TYPE, COUNT, GFP) +| +- ALLOC_ARRAY(DEV, (sizeof(E)), COUNT, GFP) ++ ALLOC_OBJS(DEV, E, COUNT, GFP) +| +- ALLOC_ARRAY(DEV, (sizeof(TYPE)), COUNT, GFP) ++ ALLOC_OBJS(DEV, TYPE, COUNT, GFP) +| +- ALLOC(DEV, struct_size(VAR, FLEX, COUNT), GFP) ++ ALLOC_FLEX(DEV, *VAR, FLEX, COUNT, GFP) +| +- ALLOC(DEV, struct_size_t(TYPE, FLEX, COUNT), GFP) ++ ALLOC_FLEX(DEV, TYPE, FLEX, COUNT, GFP) +) + +@drop_gfp_kernel depends on patch && !(file in "tools") && !(file in "samples")@ +identifier ALLOC = {devm_kmalloc_obj,devm_kmalloc_objs,devm_kmalloc_flex, + devm_kzalloc_obj,devm_kzalloc_objs,devm_kzalloc_flex, + devm_kvmalloc_obj,devm_kvmalloc_objs,devm_kvmalloc_flex, + devm_kvzalloc_obj,devm_kvzalloc_objs,devm_kvzalloc_flex}; +@@ + + ALLOC(... +- , GFP_KERNEL + ) From 7af2a51f19ca828750701054ee130cfb094706e6 Mon Sep 17 00:00:00 2001 From: Ella Ma Date: Wed, 7 Oct 2026 18:43:16 +0200 Subject: [PATCH 4/4] coccinelle: free: add a checker for `__cleanup(kfree)` usage Using __cleanup(kfree) will pass the stack address of the annotated local variable to kfree functions. This will lead to invalid deallocation issues. Inspired by CVE-2026-45959 and similar bugs recently detected. Signed-off-by: Ella Ma Signed-off-by: Julia Lawall --- scripts/coccinelle/free/cleanup-free.cocci | 112 +++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 scripts/coccinelle/free/cleanup-free.cocci diff --git a/scripts/coccinelle/free/cleanup-free.cocci b/scripts/coccinelle/free/cleanup-free.cocci new file mode 100644 index 000000000000..2aba766b5bbb --- /dev/null +++ b/scripts/coccinelle/free/cleanup-free.cocci @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Find __cleanup(kfree) +/// Using __cleanup(kfree) will pass the stack address of the annotated +/// local variable to kfree, causing an invalid free. +/// I.e., `T v __cleanup(kfree);` --> `kfree(&v);` +/// Such usage is impossible to be correct. +/// +// Confidence: High +// Copyright: (C) 2026 Ella Ma +// Options: --no-includes --include-headers + +virtual report +virtual org + +// Suggesting using __free for the functions with a DEFINE_FREE definition. +// Update this list when new DEFINE_FREE definitions are added. +@free@ +attribute name __cleanup; +symbol kfree, kfree_sensitive, kvfree, kvfree_atomic, free_percpu; +type T; +identifier v, n; +position p; +@@ + +( + T v __cleanup@p( +( + n +& +( + kfree \| kfree_sensitive \| kvfree \| kvfree_atomic \| free_percpu +) +) + ); +| + T v __cleanup@p( +( + n +& +( + kfree \| kfree_sensitive \| kvfree \| kvfree_atomic \| free_percpu +) +) + ) = ...; +) + +@script:python depends on report@ +p << free.p; +n << free.n; +@@ + +msg = f"ERROR: __cleanup({n}) will cause an invalid free: the stack address of the annotated variable will be passed to {n}; declare the variable using __free({n}) to cause the pointee to be freed" +coccilib.report.print_report(p[0], msg) + +@script:python depends on org@ +p << free.p; +n << free.n; +@@ + +msg = f"ERROR: __cleanup({n}) will cause an invalid free: the stack address of the annotated variable will be passed to {n}; declare the variable using __free({n}) to cause the pointee to be freed" +coccilib.org.print_todo(p[0], msg) + + +// Reporting __cleanup usage for the functions without a DEFINE_FREE definition. +// The following list only contains frequently used functions. Supplement it if +// necessary. +@cleanup@ +attribute name __cleanup; +symbol vfree, vfree_atomic, kfree_const; +type T; +identifier v, n; +position p; +@@ + +( + T v __cleanup@p( +( + n +& +( + vfree \| vfree_atomic \| kfree_const +) +) + ); +| + T v __cleanup@p( +( + n +& +( + vfree \| vfree_atomic \| kfree_const +) +) + ) = ...; +) + +@script:python depends on report@ +p << cleanup.p; +n << cleanup.n; +@@ + +msg = f"ERROR: __cleanup({n}) will cause an invalid free: the stack address of the annotated variable will be passed to {n}" +coccilib.report.print_report(p[0], msg) + +@script:python depends on org@ +p << cleanup.p; +n << cleanup.n; +@@ + +msg = f"ERROR: __cleanup({n}) will cause an invalid free: the stack address of the annotated variable will be passed to {n}" +coccilib.org.print_todo(p[0], msg)