From: Kees Cook <kees@kernel.org>
To: Jann Horn <jannh@google.com>
Cc: Vlastimil Babka <vbabka@suse.cz>, Miguel Ojeda <ojeda@kernel.org>,
Nathan Chancellor <nathan@kernel.org>,
Marco Elver <elver@google.com>,
Nick Desaulniers <ndesaulniers@google.com>,
Przemek Kitszel <przemyslaw.kitszel@intel.com>,
Christoph Lameter <cl@linux.com>,
Pekka Enberg <penberg@kernel.org>,
David Rientjes <rientjes@google.com>,
Joonsoo Kim <iamjoonsoo.kim@lge.com>,
Andrew Morton <akpm@linux-foundation.org>,
Roman Gushchin <roman.gushchin@linux.dev>,
Hyeonggon Yoo <42.hyeyoo@gmail.com>,
linux-kernel@vger.kernel.org, linux-mm@kvack.org,
linux-hardening@vger.kernel.org,
Andrew Pinski <pinskia@gmail.com>
Subject: Re: [PATCH 3/5] compiler_types: Introduce __is_lvalue()
Date: Sat, 22 Mar 2025 00:03:09 -0700 [thread overview]
Message-ID: <202503212348.C21AACA6@keescook> (raw)
In-Reply-To: <CAG48ez17ZtF4YESkGeegJYMTuQMsRcU3njVkNqbSbgv75jpc5g@mail.gmail.com>
On Sat, Mar 22, 2025 at 04:38:21AM +0100, Jann Horn wrote:
> On Fri, Mar 21, 2025 at 9:41 PM Kees Cook <kees@kernel.org> wrote:
> > If __builtin_is_lvalue() is available, use it with __is_lvalue(). There
> > is patch to Clang to provide this builtin now[1].
> >
> > Link: https://github.com/kees/llvm-project/commits/builtin_is_lvalue/ [1]
> > Signed-off-by: Kees Cook <kees@kernel.org>
>
> Before you land that LLVM patch, it might be a good idea to figure out
> how this interacts with the fun C quirk where you can have temporary
> rvalues which can contain array members to which you can technically
> create lvalues but must not write. As far as I understand, calling
> "kfree(getS().ptrs[0])" as in the following example would cause UB
> with your patch:
Yay UB! I can confirm that currently isModifiableLvalue() does not catch
this.
>
> ```
> $ cat kees-kfree-test.c
> #include <stdlib.h>
>
> #define __is_lvalue(expr) __builtin_is_lvalue(expr)
>
> void __kfree(void *ptr);
> void BEFORE_SET_TO_NULL();
> void AFTER_SET_TO_NULL();
> static inline void kfree_and_null(void **ptr)
> {
> __kfree(*ptr);
> BEFORE_SET_TO_NULL();
> *ptr = NULL;
> AFTER_SET_TO_NULL();
> }
> #define __force_lvalue_expr(x) \
> __builtin_choose_expr(__is_lvalue(x), x, (void *){ NULL })
> #define __free_and_null(__how, x) \
> ({ \
> typeof(x) *__ptr = &(x); \
> __how ## _and_null((void **)__ptr); \
> })
> #define __free_and_maybe_null(__how, x) \
> __builtin_choose_expr(__is_lvalue(x), \
> __free_and_null(__how, __force_lvalue_expr(x)), \
> __kfree(x))
> #define kfree(x) __free_and_maybe_null(kfree, x)
>
> struct S {
> void *ptrs[1];
> };
> struct S getS(void);
>
> int is_lvalue_test(void) {
> return __is_lvalue(getS().ptrs[0]);
> }
> void testfn2(void) {
> kfree(getS().ptrs[0]);
> }
> $ [...]/bin/clang-14 -c -o kees-kfree-test.o kees-kfree-test.c -O3 -Wall
> $ objdump -d -Mintel -r kees-kfree-test.o
>
> kees-kfree-test.o: file format elf64-x86-64
>
>
> Disassembly of section .text:
>
> 0000000000000000 <is_lvalue_test>:
> 0: b8 01 00 00 00 mov eax,0x1
> 5: c3 ret
> 6: 66 2e 0f 1f 84 00 00 cs nop WORD PTR [rax+rax*1+0x0]
> d: 00 00 00
>
> 0000000000000010 <testfn2>:
> 10: 50 push rax
> 11: e8 00 00 00 00 call 16 <testfn2+0x6>
> 12: R_X86_64_PLT32 getS-0x4
> 16: 48 89 c7 mov rdi,rax
> 19: e8 00 00 00 00 call 1e <testfn2+0xe>
> 1a: R_X86_64_PLT32 __kfree-0x4
> 1e: 31 c0 xor eax,eax
> 20: e8 00 00 00 00 call 25 <testfn2+0x15>
> 21: R_X86_64_PLT32 BEFORE_SET_TO_NULL-0x4
> 25: 31 c0 xor eax,eax
> 27: 59 pop rcx
> 28: e9 00 00 00 00 jmp 2d <testfn2+0x1d>
> 29: R_X86_64_PLT32 AFTER_SET_TO_NULL-0x4
I don't see UB manifested here, though? It looks more like a dead store
was eliminated (i.e. it was an automatic variable that wasn't going to
be referenced outside of the expression statement).
If I add a global and assign the global from *ptr, it all seems to work
fine:
--- kees-kfree-test.c.orig 2025-03-22 00:00:53.550633347 -0700
+++ kees-kfree-test.c 2025-03-21 23:58:57.124268268 -0700
@@ -2,6 +2,8 @@
#define __is_lvalue(expr) __builtin_is_modifiable_lvalue(expr)
+void *g;
+
void __kfree(void *ptr);
void BEFORE_SET_TO_NULL();
void AFTER_SET_TO_NULL();
@@ -11,6 +13,7 @@
BEFORE_SET_TO_NULL();
*ptr = NULL;
AFTER_SET_TO_NULL();
+ g = *ptr;
}
#define __force_lvalue_expr(x) \
__builtin_choose_expr(__is_lvalue(x), x, (void *){ NULL })
...
27: e8 00 00 00 00 call 2c <testfn2+0x1c>
28: R_X86_64_PLT32 AFTER_SET_TO_NULL-0x4
2c: 48 c7 05 00 00 00 00 mov QWORD PTR [rip+0x0],0x0 # 37 <testfn2+0x27>
33: 00 00 00 00
> jannh@horn:~/test/kees-kfree$
> ```
>
> As far as I understand, this causes UB in C99 ("If an attempt is made
> to modify the result of a function call or to access it after the next
> sequence point, the behavior is undefined.") and in C11 ("A non-lvalue
> expression with structure or union type, where the structure or union
> contains a member with array type (including, recursively, members of
> all contained structures and unions) refers to an object with
> automatic storage duration and temporary lifetime. 36) Its lifetime
> begins when the expression is evaluated and its initial value is the
> value of the expression. Its lifetime ends when the evaluation of the
> containing full expression or full declarator ends. Any attempt to
> modify an object with temporary lifetime results in undefined
> behavior.").
>
> Basically, something like getS().ptrs[0] gives you something that is
> technically an lvalue but must not actually be written to, and
> ->isModifiableLvalue() does not catch that.
But I agree, any mention of UB does give me pause! :)
--
Kees Cook
next prev parent reply other threads:[~2025-03-22 7:03 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-21 20:40 [RFC 0/5] slab: Set freed variables to NULL by default Kees Cook
2025-03-21 20:40 ` [PATCH 1/5] treewide: Replace kfree() casts with union members Kees Cook
2025-03-23 10:26 ` David Laight
2025-03-21 20:40 ` [PATCH 2/5] treewide: Prepare for kfree() to __kfree() rename Kees Cook
2025-03-21 20:40 ` [PATCH 3/5] compiler_types: Introduce __is_lvalue() Kees Cook
2025-03-22 3:38 ` Jann Horn
2025-03-22 7:03 ` Kees Cook [this message]
2025-03-21 20:41 ` [PATCH 4/5] slab: Set freed variables to NULL by default Kees Cook
2025-03-22 1:50 ` Jann Horn
2025-03-22 7:18 ` Kees Cook
2025-03-27 19:23 ` Jann Horn
2025-03-27 19:42 ` Matthew Wilcox
2025-03-21 20:41 ` [PATCH 5/5] [DEBUG] slab: Report number of NULLings Kees Cook
2025-03-24 16:16 ` Christoph Lameter (Ampere)
2025-03-25 19:45 ` Kees Cook
2025-03-27 13:00 ` [RFC 0/5] slab: Set freed variables to NULL by default Harry Yoo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=202503212348.C21AACA6@keescook \
--to=kees@kernel.org \
--cc=42.hyeyoo@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=cl@linux.com \
--cc=elver@google.com \
--cc=iamjoonsoo.kim@lge.com \
--cc=jannh@google.com \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=ojeda@kernel.org \
--cc=penberg@kernel.org \
--cc=pinskia@gmail.com \
--cc=przemyslaw.kitszel@intel.com \
--cc=rientjes@google.com \
--cc=roman.gushchin@linux.dev \
--cc=vbabka@suse.cz \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.