All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jan Hendrik Farr <kernel@jfarr.cc>
To: Thorsten Blum <thorsten.blum@toblux.com>
Cc: kent.overstreet@linux.dev, regressions@lists.linux.dev,
	linux-bcachefs@vger.kernel.org, linux-hardening@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [REGRESSION][BISECTED] erroneous buffer overflow detected in bch2_xattr_validate
Date: Thu, 26 Sep 2024 19:01:17 +0200	[thread overview]
Message-ID: <ZvWTXaqkmxP2E2dc@archlinux> (raw)
In-Reply-To: <A499F119-5F0C-43FC-9058-7AB92057F9B3@toblux.com>

On 26 18:09:57, Thorsten Blum wrote:
> On 26. Sep 2024, at 17:28, Thorsten Blum <thorsten.blum@toblux.com> wrote:
> > On 26. Sep 2024, at 17:14, Jan Hendrik Farr <kernel@jfarr.cc> wrote:
> >> 
> >> Hi Kent,
> >> 
> >> found a strange regression in the patch set for 6.12.
> >> 
> >> First bad commit is: 86e92eeeb23741a072fe7532db663250ff2e726a
> >> bcachefs: Annotate struct bch_xattr with __counted_by()
> >> 
> >> When compiling with clang 18.1.8 (also with latest llvm main branch) and
> >> CONFIG_FORTIFY_SOURCE=y my rootfs does not mount because there is an erroneous
> >> detection of a buffer overflow.
> >> 
> >> The __counted_by attribute is supposed to be supported starting with gcc 15,
> >> not sure if it is implemented yet so I haven't tested with gcc trunk yet.
> >> 
> >> Here's the relevant section of dmesg:
> >> 
> >> [    6.248736] bcachefs (nvme1n1p2): starting version 1.12: rebalance_work_acct_fix
> >> [    6.248744] bcachefs (nvme1n1p2): recovering from clean shutdown, journal seq 1305969
> >> [    6.252374] ------------[ cut here ]------------
> >> [    6.252375] memchr: detected buffer overflow: 12 byte read of buffer size 0
> >> [    6.252379] WARNING: CPU: 18 PID: 511 at lib/string_helpers.c:1033 __fortify_report+0x45/0x50
> >> [    6.252383] Modules linked in: bcachefs lz4hc_compress lz4_compress hid_generic usbhid btrfs crct10dif_pclmul libcrc32c crc32_pclmul crc32c_generic polyval_clmulni crc32c_intel polyval_generic raid6_pq ghash_clmulni_intel xor sha512_ssse3 sha256_ssse3 sha1_ssse3 aesni_intel gf128mul nvme crypto_simd ccp xhci_pci cryptd sp5100_tco xhci_pci_renesas nvme_core nvme_auth video wmi ip6_tables ip_tables x_tables i2c_dev
> >> [    6.252404] CPU: 18 UID: 0 PID: 511 Comm: mount Not tainted 6.11.0-10065-g6fa6588e5964 #98 d8e0beb515d91b387aa60970de7203f35ddd182c
> >> [    6.252406] Hardware name: Micro-Star International Co., Ltd. MS-7D78/PRO B650-P WIFI (MS-7D78), BIOS 1.C0 02/06/2024
> >> [    6.252407] RIP: 0010:__fortify_report+0x45/0x50
> >> [    6.252409] Code: 48 8b 34 c5 30 92 21 87 40 f6 c7 01 48 c7 c0 75 1b 0a 87 48 c7 c1 e1 93 07 87 48 0f 44 c8 48 c7 c7 ef 03 10 87 e8 0b c2 9b ff <0f> 0b e9 cf 5d 9e 00 cc cc cc cc 90 90 90 90 90 90 90 90 90 90 90
> >> [    6.252410] RSP: 0018:ffffbb3d03aff350 EFLAGS: 00010246
> >> [    6.252412] RAX: 4ce590fb7c372800 RBX: ffff98d559a400e8 RCX: 0000000000000027
> >> [    6.252413] RDX: 0000000000000002 RSI: 00000000ffffdfff RDI: ffff98e43db21a08
> >> [    6.252414] RBP: ffff98d559a400d0 R08: 0000000000001fff R09: ffff98e47ddcd000
> >> [    6.252415] R10: 0000000000005ffd R11: 0000000000000004 R12: ffff98d559a40000
> >> [    6.252416] R13: ffff98d54abf1320 R14: ffffbb3d03aff430 R15: 0000000000000000
> >> [    6.252417] FS:  00007efc82117800(0000) GS:ffff98e43db00000(0000) knlGS:0000000000000000
> >> [    6.252418] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> >> [    6.252419] CR2: 000055d96658ea80 CR3: 000000010a12c000 CR4: 0000000000f50ef0
> >> [    6.252420] PKRU: 55555554
> >> [    6.252421] Call Trace:
> >> [    6.252423]  <TASK>
> >> [    6.252425]  ? __warn+0xd5/0x1d0
> >> [    6.252427]  ? __fortify_report+0x45/0x50
> >> [    6.252429]  ? report_bug+0x144/0x1f0
> >> [    6.252431]  ? __fortify_report+0x45/0x50
> >> [    6.252433]  ? handle_bug+0x6a/0x90
> >> [    6.252435]  ? exc_invalid_op+0x1a/0x50
> >> [    6.252436]  ? asm_exc_invalid_op+0x1a/0x20
> >> [    6.252440]  ? __fortify_report+0x45/0x50
> >> [    6.252441]  __fortify_panic+0x9/0x10
> >> [    6.252443]  bch2_xattr_validate+0x13b/0x140 [bcachefs 8361179bbfcc59e669df38aec976f02d7211a659]
> >> [    6.252463]  bch2_btree_node_read_done+0x125a/0x17a0 [bcachefs 8361179bbfcc59e669df38aec976f02d7211a659]
> >> [    6.252482]  btree_node_read_work+0x202/0x4a0 [bcachefs 8361179bbfcc59e669df38aec976f02d7211a659]
> >> [    6.252499]  bch2_btree_node_read+0xa8d/0xb20 [bcachefs 8361179bbfcc59e669df38aec976f02d7211a659]
> >> [    6.252514]  ? srso_alias_return_thunk+0x5/0xfbef5
> >> [    6.252515]  ? pcpu_alloc_noprof+0x741/0xb50
> >> [    6.252517]  ? srso_alias_return_thunk+0x5/0xfbef5
> >> [    6.252519]  ? time_stats_update_one+0x75/0x1f0 [bcachefs 8361179bbfcc59e669df38aec976f02d7211a659]
> >> 
> >> ...
> >> 
> >> 
> >> The memchr in question is at:
> >> https://github.com/torvalds/linux/blob/11a299a7933e03c83818b431e6a1c53ad387423d/fs/bcachefs/xattr.c#L99
> >> 
> >> There is not actually a buffer overflow here, I checked with gdb that
> >> xattr.v->x_name does actually contain a string of the correct length and
> >> xattr.v->x_name_len contains the correct length and should be used to determine
> >> the length when memchr uses __struct_size for bounds-checking due to the
> >> __counted_by annotation.
> >> 
> >> I'm at the point where I think this is probably a bug in clang. I have a patch
> >> that does fix (more like bandaid) the problem and adds some print statements:
> >> 
> >> --
> >> diff --git a/fs/bcachefs/xattr.c b/fs/bcachefs/xattr.c
> >> index 56c8d3fe55a4..8d7e749b7dda 100644
> >> --- a/fs/bcachefs/xattr.c
> >> +++ b/fs/bcachefs/xattr.c
> >> @@ -74,6 +74,7 @@ int bch2_xattr_validate(struct bch_fs *c, struct bkey_s_c k,
> >>      enum bch_validate_flags flags)
> >> {
> >> struct bkey_s_c_xattr xattr = bkey_s_c_to_xattr(k);
> >> + const struct bch_xattr *v = (void *)k.v;
> >> unsigned val_u64s = xattr_val_u64s(xattr.v->x_name_len,
> >>  le16_to_cpu(xattr.v->x_val_len));
> >> int ret = 0;
> >> @@ -94,9 +95,12 @@ int bch2_xattr_validate(struct bch_fs *c, struct bkey_s_c k,
> >> 
> >> bkey_fsck_err_on(!bch2_xattr_type_to_handler(xattr.v->x_type),
> >> c, xattr_invalid_type,
> >> - "invalid type (%u)", xattr.v->x_type);
> >> + "invalid type (%u)", v->x_type);
> >> 
> >> - bkey_fsck_err_on(memchr(xattr.v->x_name, '\0', xattr.v->x_name_len),
> >> + pr_info("x_name_len: %d", v->x_name_len);
> >> + pr_info("__struct_size(x_name): %ld", __struct_size(v->x_name));
> >> + pr_info("__struct_size(x_name): %ld", __struct_size(xattr.v->x_name));
> >> + bkey_fsck_err_on(memchr(v->x_name, '\0', v->x_name_len),
> >> c, xattr_name_invalid_chars,
> >> "xattr name has invalid characters");
> >> fsck_err:
> >> --
> >> 
> >> 
> >> Making memchr access via a pointer created with
> >> const struct bch_xattr *v = (void *)k.v fixes it. From the print statements I
> >> can see that __struct_size(xattr.v->x_name) incorrectly returns 0, while
> >> __struct_size(v->x_name) correctly returns 10 in this case (the value of
> >> x_name_len).
> >> 
> >> The generated assembly illustrates what is going wrong. Below is an excerpt
> >> of the assembly clang generated for the bch2_xattr_validate function:
> >> 
> >> mov r13d, ecx
> >> mov r15, rdi
> >> mov r14, rsi
> >> mov rdi, offset .L.str.3
> >> mov rsi, offset .L__func__.bch2_xattr_validate
> >> mov rbx, rdx
> >> mov edx, eax
> >> call _printk
> >> movzx edx, byte ptr [rbx + 1]
> >> mov rdi, offset .L.str.4
> >> mov rsi, offset .L__func__.bch2_xattr_validate
> >> call _printk
> >> movzx edx, bh
> >> mov rdi, offset .L.str.4
> >> mov rsi, offset .L__func__.bch2_xattr_validate
> >> call _printk
> >> lea rdi, [rbx + 4]
> >> mov r12, rbx
> >> movzx edx, byte ptr [rbx + 1]
> >> xor ebx, ebx
> >> xor esi, esi
> >> call memchr
> >> 
> >> At the start of this rdx contains k.v (and is moved into rbx). The three calls
> >> to printk are the ones you can see in my patch. You can see that for the
> >> print that uses __struct_size(v->x_name) the compiler correctly uses
> >> movzx edx, byte ptr [rbx + 1]
> >> to load x_name_len into edx.
> >> 
> >> For the printk call that uses __struct_size(xattr.v->x_name) however the
> >> compiler uses
> >> movzx edx, bh
> >> So it will print the high 8 bits of the lower 16 bits (second least
> >> significant byte) of the memory address of xattr.v->x_type. This is obviously
> >> completely wrong.
> >> 
> >> It is then doing the correct call of memchr because this is using my patch.
> >> Without my patch it would be doing the same thing for the call to memchr where
> >> it uses the second least significant byte of the memory address of x_type as the
> >> length used for the bounds-check.
> >> 
> >> 
> >> 
> >> The LLVM IR also shows the same problem:
> >> 
> >> define internal zeroext i1 @xattr_cmp_key(ptr nocapture readnone %0, ptr %1, ptr nocapture noundef readonly %2) #0 align 16 {
> >> [...]
> >> %51 = ptrtoint ptr %2 to i64
> >> %52 = lshr i64 %51, 8
> >> %53 = and i64 %52, 255
> >> 
> >> This is the IR for the incorrect behavior. It simply converts the pointer to an
> >> int, shifts right by 8 bits, then and with 0xFF. If it did a load (to i64)
> >> instead of ptrtoint this would actually work, as the second least significant
> >> bit of an i64 loaded from that memory address does contain the value of
> >> x_name_len. It's as if clang forgot to dereference a pointer here.
> >> 
> >> Correct IR does this (for the other printk invocation):
> >> 
> >> define internal zeroext i1 @xattr_cmp_key(ptr nocapture readnone %0, ptr %1, ptr nocapture noundef readonly %2) #0 align 16 {
> >> [...]
> >> %4 = getelementptr inbounds %struct.bch_xattr, ptr %1, i64 0, i32 1
> >> %5 = load i8, ptr %4, align 8
> >> [...]
> >> %48 = load i8, ptr %5, align 4
> >> %49 = zext i8 %48 to i64
> >> 
> >> Best Regards
> >> Jan
> > 
> > I suspect it's the same Clang __bdos() "bug" as in [1] and [2].
> > 
> > [1] https://lore.kernel.org/linux-kernel/3D0816D1-0807-4D37-8D5F-3C55CA910FAA@linux.dev/
> > [2] https://lore.kernel.org/all/20240913164630.GA4091534@thelio-3990X/
> 
> Could you try this and see if it resolves the problem?
> 
> diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h
> index 1a957ea2f4fe..b09759f31789 100644
> --- a/include/linux/compiler_types.h
> +++ b/include/linux/compiler_types.h
> @@ -413,7 +413,7 @@ struct ftrace_likely_data {
>   * When the size of an allocated object is needed, use the best available
>   * mechanism to find it. (For cases where sizeof() cannot be used.)
>   */
> -#if __has_builtin(__builtin_dynamic_object_size)
> +#if __has_builtin(__builtin_dynamic_object_size) && !defined(__clang__)
>  #define __struct_size(p)	__builtin_dynamic_object_size(p, 0)
>  #define __member_size(p)	__builtin_dynamic_object_size(p, 1)
>  #else
> 

Alright after looking at it in the debugger the code it generates now is
just wild.

I added one more printk before the call to memchr like so:

diff --git a/fs/bcachefs/xattr.c b/fs/bcachefs/xattr.c
index 56c8d3fe55a4..3c7c479ea3a8 100644
--- a/fs/bcachefs/xattr.c
+++ b/fs/bcachefs/xattr.c
@@ -96,6 +96,7 @@ int bch2_xattr_validate(struct bch_fs *c, struct bkey_s_c k,
 			 c, xattr_invalid_type,
 			 "invalid type (%u)", xattr.v->x_type);
 
+	pr_info("__struct_size(x_name): %lu", __struct_size(xattr.v->x_name));
 	bkey_fsck_err_on(memchr(xattr.v->x_name, '\0', xattr.v->x_name_len),
 			 c, xattr_name_invalid_chars,
 			 "xattr name has invalid characters");
diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h
index f14c275950b5..43ac0bca485d 100644
--- a/include/linux/compiler_types.h
+++ b/include/linux/compiler_types.h
@@ -413,7 +413,7 @@ struct ftrace_likely_data {
  * When the size of an allocated object is needed, use the best available
  * mechanism to find it. (For cases where sizeof() cannot be used.)
  */
-#if __has_builtin(__builtin_dynamic_object_size)
+#if __has_builtin(__builtin_dynamic_object_size) && !defined(__clang__)
 #define __struct_size(p)	__builtin_dynamic_object_size(p, 0)
 #define __member_size(p)	__builtin_dynamic_object_size(p, 1)
 #else


Here's the generated assembly for this:

	mov	rdi, offset .L.str.3
	mov	rsi, offset .L__func__.bch2_xattr_validate
	mov	r12, rdx
	mov	rdx, -1
	call	_printk
	mov	rax, r12
	movzx	esi, ah
	movzx	edx, byte ptr [r12 + 1]
	cmp	rsi, rdx
	jb	.LBB4_15
# %bb.11:
	lea	rdi, [rax + 4]
	xor	ebx, ebx
	xor	esi, esi
	call	memchr

So for the printk it hardcoded -1 (aka 0xFFFFF... 64 bit long int max)
as the result of __struct_size. But then for before call to memchr it does
the same stuff again and puts the second least significant byte of the memory
address of x_type in esi, only to then load the correct value of x_name_len
into edx and compares them for the bounds-check.

Best Regards
Jan

  parent reply	other threads:[~2024-09-26 17:01 UTC|newest]

Thread overview: 62+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-09-26 15:14 [REGRESSION][BISECTED] erroneous buffer overflow detected in bch2_xattr_validate Jan Hendrik Farr
2024-09-26 15:28 ` Thorsten Blum
2024-09-26 16:09   ` Thorsten Blum
2024-09-26 16:37     ` Jan Hendrik Farr
2024-09-26 17:01     ` Jan Hendrik Farr [this message]
2024-09-26 17:45       ` Jan Hendrik Farr
2024-09-26 19:58         ` Ard Biesheuvel
2024-09-26 22:18           ` Bill Wendling
2024-09-27  1:30             ` Bill Wendling
2024-09-27  3:41               ` Jan Hendrik Farr
2024-09-28 20:50               ` Kees Cook
2024-09-28 23:33                 ` Jan Hendrik Farr
2024-09-29 19:59                   ` Jan Hendrik Farr
2024-09-28 17:36     ` Jan Hendrik Farr
2024-09-28 17:49       ` Jan Hendrik Farr
2024-09-28 20:34       ` Kees Cook
2024-10-02  9:18         ` Thorsten Blum
2024-10-03 11:33           ` Jan Hendrik Farr
2024-10-03 13:07             ` Thorsten Blum
2024-10-03 13:12               ` Jan Hendrik Farr
2024-10-03 15:02                 ` Thorsten Blum
2024-10-03 15:22                   ` Jan Hendrik Farr
2024-10-03 15:30                     ` Thorsten Blum
2024-10-03 15:35                       ` Jan Hendrik Farr
2024-10-03 15:43                         ` Thorsten Blum
2024-10-03 16:32                           ` Jan Hendrik Farr
2024-10-03 15:17               ` Jan Hendrik Farr
2024-10-03 21:28                 ` Kees Cook
2024-10-03 21:48                   ` Jan Hendrik Farr
2024-10-04 17:13                     ` Kees Cook
2024-10-07  3:56                       ` Jan Hendrik Farr
2024-10-07 15:10                         ` Jan Hendrik Farr
2024-10-16 21:13                           ` Kees Cook
2024-10-16 23:41                         ` Bill Wendling
2024-10-17  0:09                           ` Bill Wendling
2024-10-17  3:04                             ` Jan Hendrik Farr
2024-10-17 16:55                               ` Nathan Chancellor
2024-10-17 17:39                                 ` Miguel Ojeda
2024-10-17 18:55                                   ` Nathan Chancellor
2024-10-18 11:52                                     ` Miguel Ojeda
2024-10-21  1:33                                 ` Jan Hendrik Farr
2024-10-21  6:04                                   ` Miguel Ojeda
2024-10-21 17:01                                     ` Jan Hendrik Farr
2024-10-21 19:25                                   ` Nathan Chancellor
2024-10-24 13:16                                     ` Jan Hendrik Farr
2024-10-25  1:15                                       ` Nathan Chancellor
2024-10-25  8:10                                         ` Miguel Ojeda
2024-10-25 15:27                                           ` Jan Hendrik Farr
2025-05-01 14:30                                             ` Alan Huang
2025-05-01 16:45                                               ` Jan Hendrik Farr
2025-05-01 17:22                                               ` Jan Hendrik Farr
2025-05-01 17:28                                                 ` Alan Huang
2025-05-01 17:58                                                   ` Jan Hendrik Farr
2025-05-01 18:10                                                     ` Kees Cook
2025-05-01 18:18                                                     ` Alan Huang
2024-10-17  0:41                           ` Jan Hendrik Farr
2024-10-14 21:39                       ` Bill Wendling
2024-10-16  1:22             ` Bill Wendling
2024-10-16  2:18               ` Jan Hendrik Farr
2024-10-16 20:43                 ` Kees Cook
2024-10-03 21:23           ` Kees Cook
2024-10-03 22:05             ` Jan Hendrik Farr

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=ZvWTXaqkmxP2E2dc@archlinux \
    --to=kernel@jfarr.cc \
    --cc=kent.overstreet@linux.dev \
    --cc=linux-bcachefs@vger.kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=regressions@lists.linux.dev \
    --cc=thorsten.blum@toblux.com \
    /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.