I decided to actually go see how hard adding __builtin_is_lvalue() to Clang would be, and it only took an evening. With that available we can set the variables passed to kfree() to NULL automatically. This should kill a subset of "dangling pointer" Use-After-Free flaws with basically no overhead and almost no refactoring in the kernel.
https://lore.kernel.org/linux-hardening/20250321202620.work.175-kees@kernel.org/
@kees but what if the calling code wants to use the pointer value they just freed for something else? Maybe they use it as a key in some other structure and they now need to go remove that entry. By setting their passed lvalue to null won’t you break their code? Obviously an edge case, but just pointing out this trick isn’t universally safe. Obviously very useful most of the time however.
@pauldoo @kees yeah that was my thought as well. Shouldn't the compiler or static checkers be able to do more here? Check that it's unused after kfree() or not dereferenced after kfree() etc.
Also after the Linus reply on kmalloc_obj() seems he's very much against hidden side effects so maybe should have been cc'd here too :/
@pauldoo @kees i dunno if kfree() is declared in a way that makes a compiler think it’s equivalent to standard free(), but standard C says the value of a pointer becomes indeterminate after the memory it points to is freed. yes this is weird, how can free() have side effects on its arguments?! but what worries me is if the compiler marks the variable dead when it is passed to free() so if it is used after that point the compiler could treat it as uninitialized
@kees @pauldoo i don’t think any compilers currently do so, but there is some very risky stuff going on around realloc and allocation size tracking, so i expect future compiler releases to do more and more exciting things with undefined behaviour and (de)allocation functions … it worries me, so i wanted to warn against using pointers after free() - and it’s another reason that it’s a good idea to nullify them
@kees @fanf @pauldoo @pinskia GCC tracks liveness with the malloc
attribute, it doesn't do any magic based on free-like functions other than diagnostics AFAIK if the deallocator argument is also passed. But if you're not giving the deallocator argument, I don't think you'd have anything to worry about there even in terms of futureproofing.
Also, kfree
isn't a reserved name, so we can't make that assumption.
@thesamesam @kees @fanf @pauldoo @pinskia there's a tangentially related case where programs would assume that if realloc returned the same pointer value, they could continue using the old pointer variable without refreshing it (thus violating the invalidated pointer limitation in the standard), which stumbled on _FORTIFY_SOURCE because the compiler couldn't see the new size.
I would say it's safest to always assume that a freed pointer is unusable the moment it is freed.
@thesamesam @kees @fanf @pauldoo @pinskia to Kees' question, the compiler does have some diagnostics (use-after-free and some analyzer heuristics IIRC) that use the deallocator pairing to emit warnings, but it doesn't flag the freed pointers as "uninitialised".
@kees Does this work? https://godbolt.org/z/r9P3fe8q5
@vegard Oh that's very clever!
#define __is_lvalue(x) __builtin_constant_p((x) == (x))
This doesn't pass the generalized corner cases, but it does seem to work for the much smaller subset of existing kfree() arguments:
https://godbolt.org/z/oMEaK56Ts
(It still chokes on "(void *)var", which I'm hoping to fix with the built-in.)
@kees This is fine, right? https://godbolt.org/z/GEGxrhE83
@vegard Actual LOL. I mean ... it DOES work. That took me a second to parse! And here, this solves the "const" problem too. And interestingly detects variables that were _made_ constant:
https://godbolt.org/z/qv1sj67Mb
I think we can get the "main" misdetection fixed with __builtin_object_size() too, but tricks with __builtin_choose_expr() are needed. I gotta run...