slab: Allow for type introspection during allocation
Subsystems (3)
Kees Cook
maintainercover slab: Allow for type introspection during allocation
Hi,
This is an RFC for some changes I'd like to make to the kernel's allocators (starting with slab) that allow for type introspection, which has been a long-time gap in potential analysis capabilities available at compile-time. The changes here are just a "first step" example that updates kmalloc() and kzalloc() to show what I'm thinking we can do, and shows an example conversion within the fs/pstore tree.
Repeating patch 3's commit log here:
There is currently no way for the slab to know what type is being allocated, and this hampers the development of any logic that would need this information including basic type checking, alignment need analysis, etc.
Allow the size argument to optionally be a variable, from which the type (and there by the size, alignment, or any other features) can be determined at compile-time. This allows for the incremental replacement of the classic code pattern:
obj = kmalloc(sizeof(*obj), gfp);
into:
obj = kmalloc(obj, gfp);
As an additional build-time safety feature, the return value of kmalloc() also becomes typed so that the assignment and first argument cannot drift, doing away with the other, more fragile, classic code pattern:
obj = kmalloc(sizeof(struct the_object), gfp);
into:
obj = kmalloc(obj, gfp);
And any accidental variable drift will not be masked by the traditional default "void *" return value:
obj = kmalloc(something_else, gfp);
error: assignment to 'struct the_object *' from incompatible pointer type 'struct foo *' [-Wincompatible-pointer-types]
71 | obj = kmalloc(something_else, gfp);
| ^
This also opens the door for a proposed heap hardening feature that would randomize the starting offset of the allocated object within its power-of-2 bucket. Without being able to introspect the type for alignment needs, this can't be done safely (or cannot be done without significant memory usage overhead). For example, a 132 byte structure with an 8 byte alignment could be randomized into 15 locations within the 256 byte bucket: (256 - 132) / 8.
Thanks!
-Kees
Kees Cook (4):
compiler_types: Add integral/pointer type helper macros
slab: Detect negative size values and saturate
slab: Allow for type introspection during allocation
pstore: Replace classic kmalloc code pattern with typed argument
fs/pstore/blk.c | 2 +- fs/pstore/platform.c | 2 +- fs/pstore/ram.c | 3 +-- fs/pstore/ram_core.c | 2 +- fs/pstore/zone.c | 2 +- include/linux/compiler_types.h | 23 +++++++++++++++++++++++ include/linux/slab.h | 32 +++++++++++++++++++++++++------- 7 files changed, 53 insertions(+), 13 deletions(-)
Signature · 1 line
2.34.1
Kees Cook
maintainerRFC PATCH 1/4 compiler_types: Add integral/pointer type helper macros
Many compiler builtins (e.g. _Generic, __builtin_choose_expr) perform integral vs pointer argument type evaluation on outputs before evaluating the input expression. This means that all outputs must be syntactically valid for all inputs, even if the output would be otherwise filtering the types. For example, this will fail to build:
#define handle_int_or_ptr(x) \ _Generic((x), \ int: handle_int(x), \ int *: handle_ptr(x))
...
handle_int_or_ptr(7);
error: passing argument 1 of 'handle_ptr' makes pointer from integer without a cast [-Wint-conversion]
108 | handle_int_or_ptr(x);
| ^
| |
| intTo deal with this, provide helpers that force expressions into the desired type, where the mismatched cases are syntactically value, but will never actually happen:
#define handle_int_or_ptr(x) \ _Generic((x), \ int: handle_int(__force_integral_expr(x)), \ int *: handle_ptr(__force_ptr_expr(x)))
Now handle_int() only ever sees an int, and handle_ptr() only ever sees a pointer, regardless of the input type.
---
Cc: Nick Desaulniers <[email protected]>
Cc: Miguel Ojeda <[email protected]>
Cc: Marco Elver <[email protected]>
Cc: Nathan Chancellor <[email protected]>
Cc: Hao Luo <[email protected]>
Cc: Przemek Kitszel <[email protected]>
--- include/linux/compiler_types.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
include/linux/compiler_types.h +23 -0
@@ -450,6 +450,29 @@ struct ftrace_likely_data {/* Are two types/vars the same type (ignoring qualifiers)? */ #define __same_type (a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) +/* Is the variable addressable? */ +#define __is_ptr_or_array (p) \ + (__builtin_classify_type(p) == __builtin_classify_type(NULL)) + +/* Return an array decayed to a pointer. */ +#define __decay (p) \ + (&*__builtin_choose_expr(__is_ptr_or_array(p), p, NULL)) + +/* Report if variable is a pointer type. */ +#define __is_ptr (p) __same_type(p, __decay(p)) + +/* Always produce an integral expression, with specific type/vale fallback. */ +#define ___force_integral_expr (x, type, val) \ + __builtin_choose_expr(!__is_ptr(x), (x), (type)val) +#define __force_integral_expr (x) \ + ___force_integral_expr(x, int, 0) + +/* Always produce a pointer expression, with specific type/value fallback. */ +#define ___force_ptr_expr (x, type, value) \ + __builtin_choose_expr(__is_ptr(x), (x), &(type){ value }) +#define __force_ptr_expr (x) \ + __builtin_choose_expr(__is_ptr(x), (x), NULL) +/* * __unqual_scalar_typeof(x) - Declare an unqualified scalar type, leaving * non-scalar types unchanged.
Signature · 1 line
2.34.1
Kees Cook
maintainerRFC PATCH 2/4 slab: Detect negative size values and saturate
In reply to Kees CookThe allocator will already reject giant sizes seen from negative size arguments, so this commit mainly services as an example for initial type-based filtering. The size argument is checked for negative values in signed arguments, saturating any if found instead of passing them on.
For example, now the size is checked:
Before:
/* %rdi unchecked */
1eb: be c0 0c 00 00 mov $0xcc0,%esi
1f0: e8 00 00 00 00 call 1f5 <do_SLAB_NEGATIVE+0x15>
1f1: R_X86_64_PLT32 __kmalloc_noprof-0x4After:
6d0: 48 63 c7 movslq %edi,%rax
6d3: 85 ff test %edi,%edi
6d5: be c0 0c 00 00 mov $0xcc0,%esi
6da: 48 c7 c2 ff ff ff ff mov $0xffffffffffffffff,%rdx
6e1: 48 0f 49 d0 cmovns %rax,%rdx
6e5: 48 89 d7 mov %rdx,%rdi
6e8: e8 00 00 00 00 call 6ed <do_SLAB_NEGATIVE+0x1d>
6e9: R_X86_64_PLT32 __kmalloc_noprof-0x4---
Cc: Christoph Lameter <[email protected]>
Cc: Pekka Enberg <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Joonsoo Kim <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Vlastimil Babka <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: Hyeonggon Yoo <[email protected]>
Cc: [email protected]
--- include/linux/slab.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-)
include/linux/slab.h +18 -1
@@ -684,7 +684,24 @@ static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t f }return __kmalloc_noprof (size, flags); } -#define kmalloc (...) alloc_hooks(kmalloc_noprof(__VA_ARGS__)) +#define kmalloc_sized (...) alloc_hooks(kmalloc_noprof(__VA_ARGS__)) + +#define __size_force_positive (x) \ + ({ \ + typeof(__force_integral_expr(x)) __forced_val = \ + __force_integral_expr(x); \ + __forced_val < 0 ? SIZE_MAX : __forced_val; \ + }) + +#define kmalloc (p, gfp) _Generic((p), \ + unsigned char: kmalloc_sized(__force_integral_expr(p), gfp), \ + unsigned short: kmalloc_sized(__force_integral_expr(p), gfp), \ + unsigned int: kmalloc_sized(__force_integral_expr(p), gfp), \ + unsigned long: kmalloc_sized(__force_integral_expr(p), gfp), \ + signed char: kmalloc_sized(__size_force_positive(p), gfp), \ + signed short: kmalloc_sized(__size_force_positive(p), gfp), \ + signed int: kmalloc_sized(__size_force_positive(p), gfp), \ + signed long: kmalloc_sized(__size_force_positive(p), gfp))#define kmem_buckets_alloc (_b, _size, _flags) \ alloc_hooks(__kmalloc_node_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE))
Signature · 1 line
2.34.1
Re: [RFC][PATCH 2/4] slab: Detect negative size values and saturate
Quoted 55 lines · Kees Cook
The allocator will already reject giant sizes seen from negative size arguments, so this commit mainly services as an example for initial type-based filtering. The size argument is checked for negative values in signed arguments, saturating any if found instead of passing them on.
For example, now the size is checked:
Before:
/* %rdi unchecked */ 1eb: be c0 0c 00 00 mov $0xcc0,%esi 1f0: e8 00 00 00 00 call 1f5 <do_SLAB_NEGATIVE+0x15> 1f1: R_X86_64_PLT32 __kmalloc_noprof-0x4After:
6d0: 48 63 c7 movslq %edi,%rax 6d3: 85 ff test %edi,%edi 6d5: be c0 0c 00 00 mov $0xcc0,%esi 6da: 48 c7 c2 ff ff ff ff mov $0xffffffffffffffff,%rdx 6e1: 48 0f 49 d0 cmovns %rax,%rdx 6e5: 48 89 d7 mov %rdx,%rdi 6e8: e8 00 00 00 00 call 6ed <do_SLAB_NEGATIVE+0x1d> 6e9: R_X86_64_PLT32 __kmalloc_noprof-0x4Signed-off-by Kees Cook <[email protected]>---
Cc: Christoph Lameter <[email protected]>
Cc: Pekka Enberg <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Joonsoo Kim <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Vlastimil Babka <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: Hyeonggon Yoo <[email protected]>
Cc: [email protected]--- include/linux/slab.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-)include/linux/slab.h +8
-1@@ -684,7 +684,24 @@ static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t f }return __kmalloc_noprof (size, flags); } -#define kmalloc (...) alloc_hooks(kmalloc_noprof(__VA_ARGS__)) +#define kmalloc_sized (...) alloc_hooks(kmalloc_noprof(__VA_ARGS__)) + +#define __size_force_positive (x) \ + ({ \ + typeof(__force_integral_expr(x)) __forced_val = \ + __force_integral_expr(x); \ + __forced_val < 0 ? SIZE_MAX : __forced_val; \ + })
include/linux/slab.h static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t f · line 704
Quoted 10 lines
include/linux/slab.h +10
-0+ +#define kmalloc (p, gfp) _Generic((p), \ + unsigned char: kmalloc_sized(__force_integral_expr(p), gfp), \ + unsigned short: kmalloc_sized(__force_integral_expr(p), gfp), \ + unsigned int: kmalloc_sized(__force_integral_expr(p), gfp), \ + unsigned long: kmalloc_sized(__force_integral_expr(p), gfp), \ + signed char: kmalloc_sized(__size_force_positive(p), gfp), \ + signed short: kmalloc_sized(__size_force_positive(p), gfp), \ + signed int: kmalloc_sized(__size_force_positive(p), gfp), \ + signed long: kmalloc_sized(__size_force_positive(p), gfp))
I like this idea and series very much, thank you!
What about bool?
What about long long?
(by this commit one will get a rather easy to parse compile error, but the next one will obscure it a bit)
Consider the following correct (albeit somewhat weird) code:
/* header */ char *state; /* .c impl, init part */ bool needs_state = some_expr(); state = kmalloc(needs_state, GFP_KERNEL); /* .c, other part */ if (ZERO_OR_NULL_PTR(state)) return _EARLY; *state = state_machine_action(*state);
Quoted 3 lines
include/linux/slab.h +0
-0#define kmem_buckets_alloc (_b, _size, _flags) \ alloc_hooks(__kmalloc_node_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE))
Kees Cook
maintainerRe: [RFC][PATCH 2/4] slab: Detect negative size values and saturate
Quoted 60 lines · Przemek Kitszel
Quoted 59 lines · Kees Cook
The allocator will already reject giant sizes seen from negative size arguments, so this commit mainly services as an example for initial type-based filtering. The size argument is checked for negative values in signed arguments, saturating any if found instead of passing them on.
For example, now the size is checked:
Before:
/* %rdi unchecked */ 1eb: be c0 0c 00 00 mov $0xcc0,%esi 1f0: e8 00 00 00 00 call 1f5 <do_SLAB_NEGATIVE+0x15> 1f1: R_X86_64_PLT32 __kmalloc_noprof-0x4After:
6d0: 48 63 c7 movslq %edi,%rax 6d3: 85 ff test %edi,%edi 6d5: be c0 0c 00 00 mov $0xcc0,%esi 6da: 48 c7 c2 ff ff ff ff mov $0xffffffffffffffff,%rdx 6e1: 48 0f 49 d0 cmovns %rax,%rdx 6e5: 48 89 d7 mov %rdx,%rdi 6e8: e8 00 00 00 00 call 6ed <do_SLAB_NEGATIVE+0x1d> 6e9: R_X86_64_PLT32 __kmalloc_noprof-0x4Signed-off-by Kees Cook <[email protected]>---
Cc: Christoph Lameter <[email protected]>
Cc: Pekka Enberg <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Joonsoo Kim <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Vlastimil Babka <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: Hyeonggon Yoo <[email protected]>
Cc: [email protected]--- include/linux/slab.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-)include/linux/slab.h +12
-1@@ -684,7 +684,24 @@ static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t f }return __kmalloc_noprof (size, flags); } -#define kmalloc (...) alloc_hooks(kmalloc_noprof(__VA_ARGS__)) +#define kmalloc_sized (...) alloc_hooks(kmalloc_noprof(__VA_ARGS__)) + +#define __size_force_positive (x) \ + ({ \ + typeof(__force_integral_expr(x)) __forced_val = \ + __force_integral_expr(x); \ + __forced_val < 0 ? SIZE_MAX : __forced_val; \ + }) + +#define kmalloc (p, gfp) _Generic((p), \ + unsigned char: kmalloc_sized(__force_integral_expr(p), gfp), \ + unsigned short: kmalloc_sized(__force_integral_expr(p), gfp), \
Quoted 8 lines
include/linux/slab.h static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t f · line 704
Quoted 6 lines
include/linux/slab.h +6
-0+ unsigned int: kmalloc_sized(__force_integral_expr(p), gfp), \ + unsigned long: kmalloc_sized(__force_integral_expr(p), gfp), \ + signed char: kmalloc_sized(__size_force_positive(p), gfp), \ + signed short: kmalloc_sized(__size_force_positive(p), gfp), \ + signed int: kmalloc_sized(__size_force_positive(p), gfp), \ + signed long: kmalloc_sized(__size_force_positive(p), gfp))I like this idea and series very much, thank you!
Thanks!
Quoted 2 lines
What about bool?
What about long long?
Ah yes, I will add these. LKP also found a weird one (a bitfield!) that I'm fixing at the source:
https://lore.kernel.org/lkml/[email protected]/
Signature · 1 line
Kees Cook
Kees Cook
maintainerRFC PATCH 3/4 slab: Allow for type introspection during allocation
In reply to Kees CookThere is currently no way for the slab to know what type is being allocated, and this hampers the development of any logic that would need this information including basic type checking, alignment need analysis, etc.
Allow the size argument to optionally be a variable, from which the type (and there by the size, alignment, or any other features) can be determined at compile-time. This allows for the incremental replacement of the classic code pattern:
obj = kmalloc(sizeof(*obj), gfp);
into:
obj = kmalloc(obj, gfp);
As an additional build-time safety feature, the return value of kmalloc() also becomes typed so that the assignment and first argument cannot drift, doing away with the other, more fragile, classic code pattern:
obj = kmalloc(sizeof(struct the_object), gfp);
into:
obj = kmalloc(obj, gfp);
And any accidental variable drift will not be masked by the traditional default "void *" return value:
obj = kmalloc(something_else, gfp);
error: assignment to 'struct the_object *' from incompatible pointer type 'struct foo *' [-Wincompatible-pointer-types]
71 | obj = kmalloc(something_else, gfp);
| ^
This also opens the door for a proposed heap hardening feature that would randomize the starting offset of the allocated object within its power-of-2 bucket. Without being able to introspect the type for alignment needs, this can't be done safely (or cannot be done without significant memory usage overhead). For example, a 132 byte structure with an 8 byte alignment could be randomized into 15 locations within the 256 byte bucket: (256 - 132) / 8.
---
Cc: Christoph Lameter <[email protected]>
Cc: Pekka Enberg <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Joonsoo Kim <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Vlastimil Babka <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: Hyeonggon Yoo <[email protected]>
Cc: [email protected]
--- include/linux/slab.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-)
include/linux/slab.h +8 -7
@@ -685,6 +685,7 @@ static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t freturn __kmalloc_noprof (size, flags); }#define kmalloc_sized (...) alloc_hooks(kmalloc_noprof(__VA_ARGS__)) +#define kmalloc_aligned (size, align, gfp) alloc_hooks(kmalloc_noprof(size, gfp))#define __size_force_positive (x) \ ({ \
@@ -701,7 +702,10 @@ static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t f signed char: kmalloc_sized(__size_force_positive(p), gfp), \ signed short: kmalloc_sized(__size_force_positive(p), gfp), \ signed int: kmalloc_sized(__size_force_positive(p), gfp), \ - signed long: kmalloc_sized(__size_force_positive(p), gfp)) + signed long: kmalloc_sized(__size_force_positive(p), gfp), \ + default: (typeof(__force_ptr_expr(p)))kmalloc_aligned( \ + sizeof(*__force_ptr_expr(p)), \ + __alignof__(*__force_ptr_expr(p)), gfp)) #define kmem_buckets_alloc(_b, _size, _flags) \ alloc_hooks(__kmalloc_node_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE))
@@ -816,14 +820,11 @@ static inline __alloc_size(1, 2) void *kmalloc_array_node_noprof(size_t n, size_/** * kzalloc - allocate memory. The memory is set to zero. -* @size: how many bytes of memory are required. +* @p: either a pointer to an object to be allocated or bytes of memory are required. * @flags: the type of memory to allocate (see kmalloc). */ -static inline __alloc_size(1) void *kzalloc_noprof (size_t size,gfp_t flags) -{ -return kmalloc_noprof (size, flags | __GFP_ZERO); -} -#define kzalloc (...) alloc_hooks(kzalloc_noprof(__VA_ARGS__)) +#define kzalloc (_p, _flags) kmalloc(_p, (_flags)|__GFP_ZERO) +#define kzalloc_noprof (_size, _flags) kmalloc_noprof(_size, (_flags)|__GFP_ZERO)#define kzalloc_node (_size, _flags, _node) kmalloc_node(_size, (_flags)|__GFP_ZERO, _node)void *__kvmalloc_node_noprof (DECL_BUCKET_PARAMS (size ,b ),gfp_t flags,int node)__alloc_size(1) ;
Signature · 1 line
2.34.1
Kees Cook
maintainerRFC PATCH 4/4 pstore: Replace classic kmalloc code pattern with typed argument
In reply to Kees CookUsing a short Coccinelle script, it is possible to replace the classic kmalloc code patterns with the typed information:
@alloc@
type TYPE;
TYPE *P;
expression GFP;
identifier ALLOC =~ "k[mz]alloc";
@@
P = ALLOC(
- \(sizeof(*P)\|sizeof(TYPE)\), GFP)
+ P, GFP)
Show this just for kmalloc/kzalloc usage in fs/pstore as an example.
Doing this for all allocator calls in the kernel touches much more:
11941 files changed, 22459 insertions(+), 22345 deletions(-)
And obviously requires some more wrappers for kv*alloc, devm_*alloc, etc.
---
Cc: Tony Luck <[email protected]>
Cc: "Guilherme G. Piccoli" <[email protected]>
Cc: [email protected]
--- fs/pstore/blk.c | 2 +- fs/pstore/platform.c | 2 +- fs/pstore/ram.c | 3 +-- fs/pstore/ram_core.c | 2 +- fs/pstore/zone.c | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-)
fs/pstore/blk.c +1 -1
@@ -297,7 +297,7 @@ static int __init __best_effort_init(void)return -EINVAL ; } - best_effort_dev =kzalloc (sizeof (*best_effort_dev),GFP_KERNEL ); + best_effort_dev =kzalloc (best_effort_dev,GFP_KERNEL );if (!best_effort_dev)return -ENOMEM ;
fs/pstore/platform.c +1 -1
@@ -682,7 +682,7 @@ void pstore_get_backend_records(struct pstore_info *psi,struct pstore_record *record;int rc; - record =kzalloc (sizeof (*record),GFP_KERNEL ); + record =kzalloc (record,GFP_KERNEL );if (!record) {pr_err ("out of memory creating record\n" );break ;
fs/pstore/ram.c +1 -2
@@ -228,8 +228,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)*/ struct persistent_ram_zone *tmp_prz, *prz_next; - tmp_prz =kzalloc (sizeof (struct persistent_ram_zone ), -GFP_KERNEL ); + tmp_prz =kzalloc (tmp_prz,GFP_KERNEL );if (!tmp_prz)return -ENOMEM ; prz = tmp_prz;
fs/pstore/ram_core.c +1 -1
@@ -588,7 +588,7 @@ struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,struct persistent_ram_zone *prz;int ret = -ENOMEM ; - prz =kzalloc (sizeof (struct persistent_ram_zone ),GFP_KERNEL ); + prz =kzalloc (prz,GFP_KERNEL );if (!prz) {pr_err ("failed to allocate persistent ram zone\n" ); goto err;
fs/pstore/zone.c +1 -1
@@ -1165,7 +1165,7 @@ static struct pstore_zone *psz_init_zone(enum pstore_type_id type,return ERR_PTR (-ENOMEM ); } - zone =kzalloc (sizeof (struct pstore_zone ),GFP_KERNEL ); + zone =kzalloc (zone,GFP_KERNEL );if (!zone)return ERR_PTR (-ENOMEM );
Signature · 1 line
2.34.1
Re: [RFC][PATCH 4/4] pstore: Replace classic kmalloc code pattern with typed argument
Quoted 41 lines · Kees Cook
Using a short Coccinelle script, it is possible to replace the classic kmalloc code patterns with the typed information:
@alloc@
type TYPE;
TYPE *P;
expression GFP;
identifier ALLOC =~ "k[mz]alloc";
@@P = ALLOC((quoted diff) +1
-1- \(sizeof(*P)\|sizeof(TYPE)\), GFP) + P, GFP)Show this just for kmalloc/kzalloc usage in fs/pstore as an example.
Doing this for all allocator calls in the kernel touches much more:
11941 files changed, 22459 insertions(+), 22345 deletions(-)And obviously requires some more wrappers for kv*alloc, devm_*alloc, etc.
Signed-off-by Kees Cook <[email protected]>---
Cc: Tony Luck <[email protected]>
Cc: "Guilherme G. Piccoli" <[email protected]>
Cc: [email protected]--- fs/pstore/blk.c | 2 +- fs/pstore/platform.c | 2 +- fs/pstore/ram.c | 3 +-- fs/pstore/ram_core.c | 2 +- fs/pstore/zone.c | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-)fs/pstore/blk.c +0
-0
fs/pstore/blk.c static int __init __best_effort_init(void) · line 302
Quoted 7 lines
fs/pstore/blk.c +1
-1@@ -297,7 +297,7 @@ static int __init __best_effort_init(void)return -EINVAL ; } - best_effort_dev =kzalloc (sizeof (*best_effort_dev),GFP_KERNEL ); + best_effort_dev =kzalloc (best_effort_dev,GFP_KERNEL );if (!best_effort_dev)return -ENOMEM ;
I expect raised eyebrows and typical vocalizations of amusement :D -
IOW: we should consider changing the name of the macro, although I like it as is :)
other:
you repeat the pointer name twice, and code is magic anyway, so perhaps:
kzalloc_me(best_effort_dev, GFP_KERNEL);
and another variant to cover declaration-with-init.
Kees Cook
maintainerRe: [RFC][PATCH 4/4] pstore: Replace classic kmalloc code pattern with typed argument
Quoted 15 lines · Przemek Kitszel
fs/pstore/blk.c static int __init __best_effort_init(void) · line 301
Quoted 11 lines · Kees Cook
fs/pstore/blk.c +1
-1@@ -297,7 +297,7 @@ static int __init __best_effort_init(void)return -EINVAL ; } - best_effort_dev =kzalloc (sizeof (*best_effort_dev),GFP_KERNEL ); + best_effort_dev =kzalloc (best_effort_dev,GFP_KERNEL );if (!best_effort_dev)return -ENOMEM ;I expect raised eyebrows and typical vocalizations of amusement :D -
IOW: we should consider changing the name of the macro, although I like it as is :)
Yeah, I prefer keeping the name too. I feel like adding yet more macros around the allocators does not make anyone too happy. :)
Quoted 4 lines
other:
you repeat the pointer name twice, and code is magic anyway, so perhaps:kzalloc_me(best_effort_dev, GFP_KERNEL);and another variant to cover declaration-with-init.
Switch the calling style is, however, where I think it'd be good to change the name. I've had push-back in the past on changing away from the "assignment style" to the "pass by reference" style, but I would honestly prefer dropping the assignment: it is almost always redundant. (I understood the push-back to be around the case of not being able to easily use the "return kmalloc(...)" code pattern.)
It also makes it easier to deal with fixed array and flexible array variants, as the argument count can be examined to determine if this is a fixed-size struct or a flexible object:
kzalloc_struct(ptr, GFP_KERNEL); kzalloc_struct(ptr, flex_member, count, GFP_KERNEL); -> uses struct_size() to get allocation size
I wonder if we can find a way to also handle the array case at compile time:
kzalloc_struct(array, count, GFP_KERNEL);
And if so, maybe the naming should be "kzalloc_me" like you suggest, or maybe "kzalloc_obj"?
The resulting Coccinelle script gets a little more complex since we have to rewrite the matched function, but it's not bad:
@find@
type TYPE;
TYPE *P;
expression GFP;
identifier ALLOC =~ "k[mz]alloc";
@@
P = ALLOC(\(sizeof(*P)\|sizeof(TYPE)\), GFP)
@script:python rename@alloc_name << find.ALLOC;ALLOC_OBJ;
@@
coccinelle.ALLOC_OBJ = cocci.make_ident(alloc_name + "_obj")
@convert@
type find.TYPE;
TYPE *find.P;
expression find.GFP;
identifier find.ALLOC;
identifier rename.ALLOC_OBJ;
@@
- P = ALLOC(\(sizeof(*P)\|sizeof(TYPE)\), GFP)
+ ALLOC_OBJ(P, GFP)
And the results in fs/pstore/ look like this:
fs/pstore/blk.c +1 -1
@@ -297,7 +297,7 @@ static int __init __best_effort_init(void)return -EINVAL ; } - best_effort_dev =kzalloc (sizeof (*best_effort_dev),GFP_KERNEL ); +kzalloc_obj (best_effort_dev,GFP_KERNEL );if (!best_effort_dev)return -ENOMEM ;
fs/pstore/platform.c +1 -1
@@ -682,7 +682,7 @@ void pstore_get_backend_records(struct pstore_info *psi,struct pstore_record *record;int rc; - record =kzalloc (sizeof (*record),GFP_KERNEL ); +kzalloc_obj (record,GFP_KERNEL );if (!record) {pr_err ("out of memory creating record\n" );break ;
fs/pstore/ram.c +1 -2
@@ -228,8 +228,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)*/ struct persistent_ram_zone *tmp_prz, *prz_next; - tmp_prz =kzalloc (sizeof (struct persistent_ram_zone ), -GFP_KERNEL ); +kzalloc_obj (tmp_prz,GFP_KERNEL );if (!tmp_prz)return -ENOMEM ; prz = tmp_prz;
fs/pstore/ram_core.c +1 -1
@@ -588,7 +588,7 @@ struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,struct persistent_ram_zone *prz;int ret = -ENOMEM ; - prz =kzalloc (sizeof (struct persistent_ram_zone ),GFP_KERNEL ); +kzalloc_obj (prz,GFP_KERNEL );if (!prz) {pr_err ("failed to allocate persistent ram zone\n" ); goto err;
fs/pstore/zone.c +1 -1
@@ -1165,7 +1165,7 @@ static struct pstore_zone *psz_init_zone(enum pstore_type_id type,return ERR_PTR (-ENOMEM ); } - zone =kzalloc (sizeof (struct pstore_zone ),GFP_KERNEL ); +kzalloc_obj (zone,GFP_KERNEL );if (!zone)return ERR_PTR (-ENOMEM );
Signature · 1 line
Kees Cook
Roman Gushchin
reviewerQuoted 31 lines · Kees Cook
Hi,
This is an RFC for some changes I'd like to make to the kernel's allocators (starting with slab) that allow for type introspection, which has been a long-time gap in potential analysis capabilities available at compile-time. The changes here are just a "first step" example that updates
kmalloc()andkzalloc()to show what I'm thinking we can do, and shows an example conversion within the fs/pstore tree.Repeating patch 3's commit log here:
There is currently no way for the slab to know what type is being allocated, and this hampers the development of any logic that would need this information including basic type checking, alignment need analysis, etc.
Allow the size argument to optionally be a variable, from which the type (and there by the size, alignment, or any other features) can be determined at compile-time. This allows for the incremental replacement of the classic code pattern:
obj =
kmalloc(sizeof(*obj), gfp);into:
obj =
kmalloc(obj, gfp);As an additional build-time safety feature, the return value of
kmalloc()also becomes typed so that the assignment and first argument cannot drift, doing away with the other, more fragile, classic code pattern:
Quoted 5 lines
obj =
kmalloc(sizeof(struct the_object), gfp);into:
obj =
kmalloc(obj, gfp);
I like the idea, however it's not as simple and straightforward because it's common for structures to have a variable part (usually at the end) and also allocate more than one structure at once.
There are many allocations which look like
kmalloc(sizeof(my_struct) * 2 + SOME_MAGIC_LENGTH, GFP_...)
or something like this, which you can't easily convert to your scheme.
The only option I see is to introduce the new set of functions/macros, something like kmalloc_obj() or kmalloc_struct(). Or maybe tmalloc()? (t for typed)
Thanks!
Kees Cook
maintainerQuoted 38 lines · Roman Gushchin
Quoted 31 lines · Kees Cook
Hi,
This is an RFC for some changes I'd like to make to the kernel's allocators (starting with slab) that allow for type introspection, which has been a long-time gap in potential analysis capabilities available at compile-time. The changes here are just a "first step" example that updates
kmalloc()andkzalloc()to show what I'm thinking we can do, and shows an example conversion within the fs/pstore tree.Repeating patch 3's commit log here:
There is currently no way for the slab to know what type is being allocated, and this hampers the development of any logic that would need this information including basic type checking, alignment need analysis, etc.
Allow the size argument to optionally be a variable, from which the type (and there by the size, alignment, or any other features) can be determined at compile-time. This allows for the incremental replacement of the classic code pattern:
obj =
kmalloc(sizeof(*obj), gfp);into:
obj =
kmalloc(obj, gfp);As an additional build-time safety feature, the return value of
kmalloc()also becomes typed so that the assignment and first argument cannot drift, doing away with the other, more fragile, classic code pattern:Quoted 5 lines
obj =
kmalloc(sizeof(struct the_object), gfp);into:
obj =
kmalloc(obj, gfp);
Quoted 7 lines
I like the idea, however it's not as simple and straightforward because it's common for structures to have a variable part (usually at the end) and also allocate more than one structure at once.
There are many allocations which look like
kmalloc(sizeof(my_struct) * 2 + SOME_MAGIC_LENGTH, GFP_...)or something like this, which you can't easily convert to your scheme.
Right -- and with this we can leave those as-is initially (since a size argument will still work).
Quoted 3 lines
The only option I see is to introduce the new set of functions/macros, something like
kmalloc_obj()orkmalloc_struct(). Or maybetmalloc()? (t for typed)
Yeah, in a neighboring thread I was talking about a kmalloc_obj that would handle fixed-sized structs, flexible array structs, and arrays. I need to prove out the array part, but the first two should be trivial to implement.
Signature · 1 line
Kees Cook
Christoph Lameter (Ampere)
reviewerQuoted 2 lines · Kees Cook
obj = kmalloc(obj, gfp);Could we avoid repeating "obj" in this pattern?
F.e.
KMALLOC(obj, gfp);
instead?
Kees Cook
maintainerQuoted 10 lines · Christoph Lameter (Ampere)
Quoted 2 lines · Kees Cook
obj = kmalloc(obj, gfp);Could we avoid repeating "obj" in this pattern?
F.e.
KMALLOC(obj, gfp);
This appears to be the common feedback, which is good! :) And we can still have it return "obj" as well, so it could still be used in "return" statements, etc. I will work up a new RFC...
Signature · 1 line
Kees Cook
Quoted 9 lines · Kees Cook
Quoted 7 lines · Christoph Lameter (Ampere)
Quoted 2 lines · Kees Cook
obj = kmalloc(obj, gfp);Could we avoid repeating "obj" in this pattern?
Quoted 7 lines
Quoted 3 lines
F.e.
KMALLOC(obj, gfp);This appears to be the common feedback, which is good! :) And we can still have it return "obj" as well, so it could still be used in "return" statements, etc. I will work up a new RFC...
More macros like this only obfuscate the code further. The name would become something that makes it really clear there's an assignment.
assign_kmalloc(obj, gfp)
There may be better options. Also ALLCAPS could be avoided here, as we have done with other language-like features (vs. pure constants).
Kees Cook
maintainerQuoted 18 lines · Marco Elver
Quoted 9 lines · Kees Cook
Quoted 7 lines · Christoph Lameter (Ampere)
Quoted 2 lines · Kees Cook
obj = kmalloc(obj, gfp);Could we avoid repeating "obj" in this pattern?
Quoted 7 lines
Quoted 3 lines
F.e.
KMALLOC(obj, gfp);This appears to be the common feedback, which is good! :) And we can still have it return "obj" as well, so it could still be used in "return" statements, etc. I will work up a new RFC...
Quoted 7 lines
More macros like this only obfuscate the code further. The name would become something that makes it really clear there's an assignment.
assign_kmalloc(obj, gfp)There may be better options. Also ALLCAPS could be avoided here, as we have done with other language-like features (vs. pure constants).
So, in looking a code patterns, it seems what we really want more than returning the object that was allocated is actually returning the size of the allocation size requested. i.e.:
info->size = struct_size(ptr, flex_member, count); info->obj = kmalloc(info->size, gfp);
would become:
info->size = kmalloc(info->obj, flex_member, count, gfp);
-Kees
Signature · 1 line
Kees Cook
Quoted 31 lines · Kees Cook
Quoted 18 lines · Marco Elver
Quoted 9 lines · Kees Cook
Quoted 7 lines · Christoph Lameter (Ampere)
Quoted 2 lines · Kees Cook
obj = kmalloc(obj, gfp);Could we avoid repeating "obj" in this pattern?
Quoted 7 lines
Quoted 3 lines
F.e.
KMALLOC(obj, gfp);This appears to be the common feedback, which is good! :) And we can still have it return "obj" as well, so it could still be used in "return" statements, etc. I will work up a new RFC...
Quoted 7 lines
More macros like this only obfuscate the code further. The name would become something that makes it really clear there's an assignment.
assign_kmalloc(obj, gfp)There may be better options. Also ALLCAPS could be avoided here, as we have done with other language-like features (vs. pure constants).
So, in looking a code patterns, it seems what we really want more than returning the object that was allocated is actually returning the size of the allocation size requested. i.e.:
Quoted 9 lines
info->size = struct_size(ptr, flex_member, count); info->obj = kmalloc(info->size, gfp);would become:
info->size = kmalloc(info->obj, flex_member, count, gfp);-Kees
that will work out also for the (IMO) most common case of checking if the allocation succeeded:
if (!kmalloc(my_foo, flex_part, count, gfp)) return -ENOMEM;