From nobody Fri Apr 3 04:34:28 2026 Received: from mail-pl1-f179.google.com (mail-pl1-f179.google.com [209.85.214.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 77FC620C490 for ; Wed, 25 Mar 2026 00:43:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774399440; cv=none; b=JyPKhMtHRQPe7r+7s7ZnXmiA35EXYWOMtqCdeUqnKsfH/GdASkEbj1PYFrc2sUF6/RluiIAXwKqY7fmfPQq3mt258ZxLlkAD/agV7nWATe67umMIjYT2P599TQ+USmMOiofFq+O9A6V8MP6Vh6mGenl9HUzFsD5Q4cfR/vV4Ybc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774399440; c=relaxed/simple; bh=Nd0atCCstaJ2Fwk9EQFaGAl7hAjSKTfwkXCd4sAxmjA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hImT1h4VJPNfS+6fpEmRq2XlO1JKtDz6vsuKd060iqk27UKixM71Khqa15AgrAnynomRC+cnjkvQ7fJ13p76VPCST6/4N1FN7/OcC0FAoI0rpA1zNxNWkTDeMsTRqZP3hUZcOk4ICcu9xjAf3we+hqIVPxZw25AMZsozGWRiqR0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ja/75nDU; arc=none smtp.client-ip=209.85.214.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ja/75nDU" Received: by mail-pl1-f179.google.com with SMTP id d9443c01a7336-2a9296b3926so14991235ad.1 for ; Tue, 24 Mar 2026 17:43:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1774399435; x=1775004235; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=LTSd+cTo/0b497i3cXRpwad9mDPRGXICrLQ2yK5ereM=; b=ja/75nDUAZ8RNoAKD+mpQPfUtmkqwOwoUyNIV6bjCKpRoYoBQZlYR++w+LsYUXpzQI vEdDse2AqqK+2OKRu+PkqdTnBZ2eKr+p+PvVmiDxkH6sLwTvMMZWEXvHoGFiO2FLUuik Xr8Duz1OL41FE1GDnVQHpfjjYVbP7Qys0kB9sYTRIo84/PTA2Rrh+zffh9NDf2AMvbo2 sm9ZUj4kik/AxydwGRAdqK3tYrm/tuZ8C1DCZbIp9MFhFzwdv6CwrcUt+1wytM1fHBtN pVR9pagKvYP9LYS6r6mBKYqh83Gl59Ui3WlQpPgW39p5NpdibYQWL063URos4f2uk/PN b4qA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774399435; x=1775004235; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=LTSd+cTo/0b497i3cXRpwad9mDPRGXICrLQ2yK5ereM=; b=GGb81FANHkNdRY8IVVz4SRSUvYbqTFe6+qCn4FrSgcvQkd1lKbNL5r4+vhqIO8PfQg 7i9t3S1cq+ewSE+U5slmhPINAX5BVb1YiSvViTKlmfiNKtAI8kgIVT3EcpzI25MSvJj2 89oXoOPLREhizUGfRlXZ176pZSa6DiOpZXkCUbwxH89aVQQL1ox/ysR+P1EJDinMnFRj DYVrbUMSWEXwpALwvTC8PQSV+8tF5VkzKEGwYHRd6rz3Zth2gbgIF6U48CQLPQkueM2S ST7C8pLwlY8piH4xiex6ffql9RhsqEOLxZGt1StrCZVw+l9ErlcGy6ZMQqN4CCzGz2wk ZmrA== X-Forwarded-Encrypted: i=1; AJvYcCWwuC8AvC3Wwiu0EJN/ehR22AEmzeR/VX6Nii/Gh4HaQNFAAKzHk+xL14GQOCsQawtCyPQGegWQO9W9cv8=@vger.kernel.org X-Gm-Message-State: AOJu0Yz+JgMzt5Zet9BWtRwyr8WBQqCR51NYmJHKto600fhKdBj2GuYw 5fQqVVV69kW4f0hkhLEI2QEvZWHPF+MD1I23CpTtYAjSH5NBZma6B8bY X-Gm-Gg: ATEYQzwcWfpdzvhdM3N7M57vxeQvZLAFWsOGWdyAR5iZaauc0tfWRUrfMed7vA0TLjp nE42zleMjYu4YSRGQs+yXK1GXA6cvnMj80y1E2iMnQ7sSle4W7SDkxNP9e0LmfGiCKVtfI7tpzV iSQNjuuiPvBOVMxaUPkf46aU2i39KMMBx3nbyXHfs+RDJL6uIQIOWHLGQYTRg/IyTXkSAv6qxig xa0bSMm0xWuNhM/MyVArGHyqNax2+hkfozt5m66tJjCkF/TNtoiPSqR4FEeFB3gymMc76HOTXBE KoM1hZx+nlI1vsvlXhIefbnGDLYWWoxL6wt3bfbcfmjSai/VAq7xt8tmujmnYriVxgsHCp8cISG h8qgZ9xzPiGa5WAmQTC5CbmR/PWuxAsxD07CrWN4WoQ8ODYCRu0+ezagMDtxQoJTEJxGIODJzKV TyRvmMz0hgmIIreTreny7aqqhpdCM9LGrUehtdldbHDRtYh9r0TvQ7iEAzP37F/30ntyETd8rZ3 jEwPatGLA== X-Received: by 2002:a17:903:22d2:b0:2b0:4f82:74ce with SMTP id d9443c01a7336-2b0b0b2f889mr15838615ad.46.1774399434637; Tue, 24 Mar 2026 17:43:54 -0700 (PDT) Received: from kernel-fuzz.. ([103.172.182.26]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0836554acsm207457015ad.51.2026.03.24.17.43.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Mar 2026 17:43:53 -0700 (PDT) From: ZhengYuan Huang To: dsterba@suse.com, clm@fb.com, idryomov@gmail.com Cc: linux-btrfs@vger.kernel.org, linux-kernel@vger.kernel.org, baijiaju1990@gmail.com, r33s3n6@gmail.com, zzzccc427@gmail.com, ZhengYuan Huang Subject: [PATCH v3 1/4] btrfs: balance: fix null-ptr-deref in chunk_usage_filter Date: Wed, 25 Mar 2026 08:43:36 +0800 Message-ID: <20260325004339.2323838-2-gality369@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260325004339.2323838-1-gality369@gmail.com> References: <20260325004339.2323838-1-gality369@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" [BUG] Running btrfs balance with a usage filter (-dusage=3DN) can trigger a null-ptr-deref when metadata corruption causes a chunk to have no corresponding block group in the in-memory cache: KASAN: null-ptr-deref in range [0x0000000000000070-0x0000000000000077] RIP: 0010:chunk_usage_filter fs/btrfs/volumes.c:3874 [inline] RIP: 0010:should_balance_chunk fs/btrfs/volumes.c:4018 [inline] RIP: 0010:__btrfs_balance fs/btrfs/volumes.c:4172 [inline] RIP: 0010:btrfs_balance+0x2024/0x42b0 fs/btrfs/volumes.c:4604 ... Call Trace: btrfs_ioctl_balance fs/btrfs/ioctl.c:3577 [inline] btrfs_ioctl+0x25cf/0x5b90 fs/btrfs/ioctl.c:5313 vfs_ioctl fs/ioctl.c:51 [inline] ... The bug is reproducible on next-20260312. [CAUSE] Two separate data structures are involved: 1. The on-disk chunk tree, which records every chunk (logical address space region) and is iterated by __btrfs_balance(). 2. The in-memory block group cache (fs_info->block_group_cache_tree), which is built at mount time by btrfs_read_block_groups() and holds a struct btrfs_block_group for each chunk. This cache is what the usage filter queries. On a well-formed filesystem, these two are kept in 1:1 correspondence. However, btrfs_read_block_groups() builds the cache from block group items in the extent tree, not directly from the chunk tree. A corrupted image can therefore contain a chunk item in the chunk tree whose corresponding block group item is absent from the extent tree; that chunk's block group is then never inserted into the in-memory cache. When balance iterates the chunk tree and reaches such an orphaned chunk, should_balance_chunk() calls chunk_usage_filter(), which queries the block group cache: cache =3D btrfs_lookup_block_group(fs_info, chunk_offset); chunk_used =3D cache->used; /* cache may be NULL */ btrfs_lookup_block_group() returns NULL silently when no cached entry covers chunk_offset. chunk_usage_filter() does not check the return value, so the immediately following dereference of cache->used triggers the crash. [FIX] Add a NULL check after btrfs_lookup_block_group() in chunk_usage_filter(). When the lookup fails, emit a btrfs_err() message identifying the affected bytenr and return -EUCLEAN to indicate filesystem corruption. Since chunk_usage_filter() now has an error path, change its return type from bool to int: negative errno on error, 0 if the chunk passes the usage filter, and 1 if it should be skipped. Update should_balance_chunk() accordingly to propagate negative errors from the usage filter path while still returning 0 for chunks that should not be balanced and 1 for chunks that should be balanced. Finally, handle the new negative return in __btrfs_balance() by jumping to the existing error path, which aborts the balance operation and reports the error to userspace. After the fix, the same corruption is correctly detected and reported by the filter, and the null-ptr-deref is no longer triggered. Fixes: 5ce5b3c0916b ("Btrfs: usage filter") Signed-off-by: ZhengYuan Huang --- fs/btrfs/volumes.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 2bec544d8ba3..1eca5fa6bdaa 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -3863,14 +3863,20 @@ static bool chunk_usage_range_filter(struct btrfs_f= s_info *fs_info, u64 chunk_of return ret; } =20 -static bool chunk_usage_filter(struct btrfs_fs_info *fs_info, u64 chunk_of= fset, - struct btrfs_balance_args *bargs) +static int chunk_usage_filter(struct btrfs_fs_info *fs_info, u64 chunk_off= set, + struct btrfs_balance_args *bargs) { struct btrfs_block_group *cache; u64 chunk_used, user_thresh; - bool ret =3D true; + int ret =3D 1; =20 cache =3D btrfs_lookup_block_group(fs_info, chunk_offset); + if (!cache) { + btrfs_err(fs_info, + "balance: chunk at bytenr %llu has no corresponding block group", + chunk_offset); + return -EUCLEAN; + } chunk_used =3D cache->used; =20 if (bargs->usage_min =3D=3D 0) @@ -3881,7 +3887,7 @@ static bool chunk_usage_filter(struct btrfs_fs_info *= fs_info, u64 chunk_offset, user_thresh =3D mult_perc(cache->length, bargs->usage); =20 if (chunk_used < user_thresh) - ret =3D false; + ret =3D 0; =20 btrfs_put_block_group(cache); return ret; @@ -3986,8 +3992,8 @@ static bool chunk_soft_convert_filter(u64 chunk_type,= struct btrfs_balance_args return false; } =20 -static bool should_balance_chunk(struct extent_buffer *leaf, struct btrfs_= chunk *chunk, - u64 chunk_offset) +static int should_balance_chunk(struct extent_buffer *leaf, struct btrfs_c= hunk *chunk, + u64 chunk_offset) { struct btrfs_fs_info *fs_info =3D leaf->fs_info; struct btrfs_balance_control *bctl =3D fs_info->balance_ctl; @@ -4014,9 +4020,13 @@ static bool should_balance_chunk(struct extent_buffe= r *leaf, struct btrfs_chunk } =20 /* usage filter */ - if ((bargs->flags & BTRFS_BALANCE_ARGS_USAGE) && - chunk_usage_filter(fs_info, chunk_offset, bargs)) { - return false; + if (bargs->flags & BTRFS_BALANCE_ARGS_USAGE) { + int ret2 =3D chunk_usage_filter(fs_info, chunk_offset, bargs); + + if (ret2 < 0) + return ret2; + if (ret2) + return false; } else if ((bargs->flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) && chunk_usage_range_filter(fs_info, chunk_offset, bargs)) { return false; @@ -4172,6 +4182,10 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_= info) ret =3D should_balance_chunk(leaf, chunk, found_key.offset); =20 btrfs_release_path(path); + if (ret < 0) { + mutex_unlock(&fs_info->reclaim_bgs_lock); + goto error; + } if (!ret) { mutex_unlock(&fs_info->reclaim_bgs_lock); goto loop; --=20 2.43.0