From nobody Sat Jul 25 06:09:22 2026 Received: from mail-pj1-f41.google.com (mail-pj1-f41.google.com [209.85.216.41]) (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 4C5DB379C5F for ; Fri, 17 Jul 2026 04:40:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784263252; cv=none; b=qd7rfrg3bl+tFMOB3OHxfYDFfEE8kGIcjIZZ0rPg8J51xO3lhkFjqQnkJRQuE6suGUAasbdtL+WUZL35Do/U0kE4pLEoEJE8wzBAWQr+9lfi/Kp1hfuv4ry+LjpdT2R2fOPBsApUE4gYHl8ZgIuH/DUVHzxr1D/pwcOsV8PG624= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784263252; c=relaxed/simple; bh=ibjRgNu+sb+l/5C/6V9yv9kgZ6Wvymzgh2mWjoJawNw=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=Risg2YjWZNi1r4DzLGqjZGHmmz5ahz4OmiOxrBltdlA0gE0tEGvnI1p4yLmrG/Tg6oOhIQ66LmfVLpdlH/Jt1j7SdZu6n/IpzuT/GzQORS/FVKi9gQHeA9nGiU1Z4cxWI4jzAqype0Jo/y/6R1GSHe48q52AXkLMQtZ3GDe5zJE= 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=Qoq+6bDc; arc=none smtp.client-ip=209.85.216.41 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="Qoq+6bDc" Received: by mail-pj1-f41.google.com with SMTP id 98e67ed59e1d1-38dc4553f62so5893385a91.0 for ; Thu, 16 Jul 2026 21:40:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784263248; x=1784868048; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=5ebZeX2Y3/4ooTohYbbIM7IFOTRj/UajhZ6G3Yjs6Hw=; b=Qoq+6bDcHOORAD6YNDEmX8pY1MfUzTrQIhHB3LltoR4seWrjorLTGCoDg32XEmOfna 91g83/i7UrtWW+lo8YCrblK2pdbcUOxinnHsQEiX/1AS5SrZMbC/bt2evHGoXR8XKg1p pugOwigeCtMINbB1IgNjAPyVhmEg6DtljZE6ReMNoozbZmnTX9D0K35ZHpQptvhDrBA1 /o7PVSBC4fXqmJiv2my4sKvAnx9h+oBxW95PO4sJWqTvSs/btV9ZVJ93dsFE7qLadlf/ sO57kHEa7tFa0pUbjJm2JG7VPTJRlIFVbJ8bKLyJ4htS+f9KrCMlFKW9ja4DZgsGEhCX fUyQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784263248; x=1784868048; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=5ebZeX2Y3/4ooTohYbbIM7IFOTRj/UajhZ6G3Yjs6Hw=; b=QlTuE1l/zxVMV3L12q5JPy9ZYfr7fSOzRqRPAR3O/zWJDOdiqysdXIPng8XmHP+/gP cmL1h2uXvejWnRXDPaRqpFglmy2XJ7ao3/sw7RsDhTeaO4LpoxBhuTbeJYu3Iw/H0wcY /o7iIHzq7/4rB3mbhC0SXq1YcpuY3yG2aYone/xyosv9Kpe0pLSv6Bj4dL2SNyey306y uaVYKuzp5Q/gonug6TqihsGGBhPx61blL1ByJ11zVK2jE54aQnn0CLB9i7aluE5fAVqc TjsN5EZZ3a4XM24nd9E5EWSczw5jq3W9EedLkJ3/T14Ht4odULi86ZOww8ljShDI4XM/ H/pg== X-Forwarded-Encrypted: i=1; AHgh+RpSTAFzikyZfze8REB5tUsi+u6Cywj55gWdDHs9phTwqq0h3wibdn263maJsD6iqxaZyF+siLUQYeQpxgY=@vger.kernel.org X-Gm-Message-State: AOJu0YxThuLEWvfQQzLKEsj8O+sNq6dTTqmoTXI2z9ZDxn9E2/ltFj91 3C7YZ+WXaPiQj6Wj0cHD9gVN3ELUbw2Kvc/021W4eTgxwMgTLSVBZb2y X-Gm-Gg: AfdE7ckyq2oBUNkoFWtYObtgp+glwHSsTKiGO5iEsrWCOZkyyoDHA9oHVr08SFpdYkx oDRUEsCXUlhcJ0bSvxEYgP1AXXwzE0HbgzMFyOq1ZIK/pgf3yYK83OdDJeZDahddxhNw0xg8MHc aM4p8cs3Kp2Rrf5W02D6/RiPStlf60e6Xt2q/UFDbhGwzMWMOyaj3DTkbfqtgAkUGuqubS++T1g XKCx0xLmR4JnqsZQJ8ymZvckMZZoFAJAs74lmtmIVzvlVjFvTW8WFmpTPPF0DATiqhO15nFJ2sS GVN7DtjmCmU58qXwUgv9AEA4xtNkRnSMshLu9DUSYnf2FtWzYNmLvxLUMjqFtM3EEPTwloSYD+1 0T1dv0tVvsgtj9N8IR8JuJkr6aTAgKIHg69isrZHEx/fFP7oSMouPWf+7vWuyE2n5zPytIsorKi S8Iqd6BWaAXAAEhpJ5Ao7J+Z0tP2DUc5Doe4wFe0IduhbQDQ== X-Received: by 2002:a17:90b:2703:b0:38e:2e16:859b with SMTP id 98e67ed59e1d1-38e4b513e04mr875584a91.20.1784263248080; Thu, 16 Jul 2026 21:40:48 -0700 (PDT) Received: from carrot.taila25129.ts.net (madb688455.ap.nuro.jp. [219.104.132.85]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38e4b0e9b9dsm339642a91.9.2026.07.16.21.40.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 Jul 2026 21:40:47 -0700 (PDT) From: Ryusuke Konishi To: Viacheslav Dubeyko Cc: linux-nilfs , LKML , Shuangpeng Bai Subject: [PATCH] nilfs2: fix slab-out-of-bounds in nilfs_direct_propagate after truncation Date: Fri, 17 Jul 2026 13:39:43 +0900 Message-ID: <20260717044045.137808-1-konishi.ryusuke@gmail.com> X-Mailer: git-send-email 2.43.0 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" Shuangpeng Bai reported that KASAN detected a slab-out-of-bounds error in nilfs_direct_propagate() during testing. Analysis revealed that after truncating a file, a node block immediately below the B-tree root was not deleted. Instead, it remained in the B-tree node cache in a dirty state. The log writer subsequently detected this block and incorrectly invoked nilfs_direct_propagate() on it, which is designed to handle only data blocks in direct mapping. B-tree nodes in the cache are managed by virtual block numbers, and their logical keys typically exceed the range expected by direct mapping. Consequently, processing such a node as a direct mapping entry triggers a slab-out-of-bounds access. The root cause is that when a B-tree mapping collapses into a direct mapping during truncation, an intermediate node block pointed to by the root node is left behind as garbage instead of being explicitly deleted. This resolves the issue by adding a nilfs_btree_discard() operation to delete the remaining intermediate node block during the conversion. A 'deform' flag is added to the bop_delete interface to explicitly signal that the deletion is part of a mapping transformation. This allows the B-tree mapping implementation to perform the necessary cleanup and discarding of the residual node structure that would be otherwise be left orphaned after the transition. Reported-by: Shuangpeng Bai Closes: https://lore.kernel.org/r/08A3603A-ADB6-484C-9015-9AC1340E6FB8@gmai= l.com Fixes: 36a580eb489f ("nilfs2: direct block mapping") Cc: stable@vger.kernel.org Signed-off-by: Ryusuke Konishi --- Hi Viacheslav, Please apply this for the next cycle. This fixes a flaw in the original B-tree implementation related to truncati= on and resolves the reported out-of-bounds memory access issue. Thanks, Ryusuke Konishi fs/nilfs2/bmap.c | 2 +- fs/nilfs2/bmap.h | 2 +- fs/nilfs2/btree.c | 39 ++++++++++++++++++++++++++++++++------- fs/nilfs2/direct.c | 4 ++-- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/fs/nilfs2/bmap.c b/fs/nilfs2/bmap.c index 5f0f1f283af0..83f6ea30cc8b 100644 --- a/fs/nilfs2/bmap.c +++ b/fs/nilfs2/bmap.c @@ -175,7 +175,7 @@ static int nilfs_bmap_do_delete(struct nilfs_bmap *bmap= , __u64 key) return ret; } =20 - return bmap->b_ops->bop_delete(bmap, key); + return bmap->b_ops->bop_delete(bmap, key, false); } =20 /** diff --git a/fs/nilfs2/bmap.h b/fs/nilfs2/bmap.h index 4656df392722..a72f3c308a5d 100644 --- a/fs/nilfs2/bmap.h +++ b/fs/nilfs2/bmap.h @@ -63,7 +63,7 @@ struct nilfs_bmap_operations { int (*bop_lookup_contig)(const struct nilfs_bmap *, __u64, __u64 *, unsigned int); int (*bop_insert)(struct nilfs_bmap *, __u64, __u64); - int (*bop_delete)(struct nilfs_bmap *, __u64); + int (*bop_delete)(struct nilfs_bmap *bmap, __u64 key, bool deform); void (*bop_clear)(struct nilfs_bmap *); =20 int (*bop_propagate)(struct nilfs_bmap *, struct buffer_head *); diff --git a/fs/nilfs2/btree.c b/fs/nilfs2/btree.c index 64d5f7c5ab44..64bac66af25b 100644 --- a/fs/nilfs2/btree.c +++ b/fs/nilfs2/btree.c @@ -1425,6 +1425,28 @@ static void nilfs_btree_shrink(struct nilfs_bmap *bt= ree, path[level].bp_bh =3D NULL; } =20 +/** + * nilfs_btree_discard - discard the last node for the mapping transformat= ion + * @btree: bmap struct of btree + * @path: array of nilfs_btree_path struct + * @level: level of the B-tree node being operated on + * @keyp: argument for passing a key (unused) + * @ptrp: argument for passing a pointer (unused) + */ +static void nilfs_btree_discard(struct nilfs_bmap *btree, + struct nilfs_btree_path *path, int level, + __u64 *keyp, __u64 *ptrp) +{ + struct nilfs_btree_node *root =3D nilfs_btree_get_root(btree); + + nilfs_btree_node_delete(root, 0, NULL, NULL, + NILFS_BTREE_ROOT_NCHILDREN_MAX); + nilfs_btree_node_set_level(root, level); + + nilfs_btnode_delete(path[level].bp_bh); + path[level].bp_bh =3D NULL; +} + static void nilfs_btree_nop(struct nilfs_bmap *btree, struct nilfs_btree_path *path, int level, __u64 *keyp, __u64 *ptrp) @@ -1435,7 +1457,7 @@ static int nilfs_btree_prepare_delete(struct nilfs_bm= ap *btree, struct nilfs_btree_path *path, int *levelp, struct nilfs_bmap_stats *stats, - struct inode *dat) + struct inode *dat, bool deform) { struct buffer_head *bh; struct nilfs_btree_node *node, *parent, *sib; @@ -1522,15 +1544,17 @@ static int nilfs_btree_prepare_delete(struct nilfs_= bmap *btree, if (nilfs_btree_node_get_nchildren(node) - 1 <=3D NILFS_BTREE_ROOT_NCHILDREN_MAX) { path[level].bp_op =3D nilfs_btree_shrink; - stats->bs_nblocks +=3D 2; - level++; - path[level].bp_op =3D nilfs_btree_nop; - goto shrink_root_child; + } else if (deform) { + path[level].bp_op =3D nilfs_btree_discard; } else { path[level].bp_op =3D nilfs_btree_do_delete; stats->bs_nblocks++; goto out; } + stats->bs_nblocks +=3D 2; + level++; + path[level].bp_op =3D nilfs_btree_nop; + goto shrink_root_child; } } =20 @@ -1581,7 +1605,7 @@ static void nilfs_btree_commit_delete(struct nilfs_bm= ap *btree, nilfs_bmap_set_dirty(btree); } =20 -static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key) +static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key, bool de= form) =20 { struct nilfs_btree_path *path; @@ -1601,7 +1625,8 @@ static int nilfs_btree_delete(struct nilfs_bmap *btre= e, __u64 key) =20 dat =3D NILFS_BMAP_USE_VBN(btree) ? nilfs_bmap_get_dat(btree) : NULL; =20 - ret =3D nilfs_btree_prepare_delete(btree, path, &level, &stats, dat); + ret =3D nilfs_btree_prepare_delete(btree, path, &level, &stats, dat, + deform); if (ret < 0) goto out; nilfs_btree_commit_delete(btree, path, level, dat); diff --git a/fs/nilfs2/direct.c b/fs/nilfs2/direct.c index 8bd0b1374e25..b8643d3aa2f8 100644 --- a/fs/nilfs2/direct.c +++ b/fs/nilfs2/direct.c @@ -144,7 +144,7 @@ static int nilfs_direct_insert(struct nilfs_bmap *bmap,= __u64 key, __u64 ptr) return ret; } =20 -static int nilfs_direct_delete(struct nilfs_bmap *bmap, __u64 key) +static int nilfs_direct_delete(struct nilfs_bmap *bmap, __u64 key, bool de= form) { union nilfs_bmap_ptr_req req; struct inode *dat; @@ -234,7 +234,7 @@ int nilfs_direct_delete_and_convert(struct nilfs_bmap *= bmap, /* no need to allocate any resource for conversion */ =20 /* delete */ - ret =3D bmap->b_ops->bop_delete(bmap, key); + ret =3D bmap->b_ops->bop_delete(bmap, key, true); if (ret < 0) return ret; =20 --=20 2.43.0