From 4f3fb9e08ba5f21254b4d542c699b5fba8158d54 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 16 Dec 2025 16:23:23 +1030 Subject: [PATCH 1/5] btrfs-progs: enhance detection on unknown inode keys There is a bug report that a bitflip corrupted one tree block, causing a corruption that can not be repaired by btrfs-check. The corruption looks like this: item 10 key (730455 INODE_ITEM 0) itemoff 15456 itemsize 160 generation 7280 transid 9794 size 13829 nbytes 16384 block group 0 mode 100600 links 1 uid 1000 gid 1000 rdev 0 sequence 11 flags 0x0(none) atime 1765397443.29231914 (2025-12-11 06:40:43) ctime 1764798591.882909548 (2025-12-04 08:19:51) mtime 1764798591.882909548 (2025-12-04 08:19:51) otime 1764712848.413821734 (2025-12-03 08:30:48) item 11 key (730455 UNKNOWN.8 1924) itemoff 15406 itemsize 50 Note item 11, it has a unknown key, but the itemsize indicates it's an INODE_REF with 40 name_len (which matches the DIR_ITEM). bin(BTRFS_INODE_REF_KEY) = 0b1100 bin(8) = 0b1000 So it's indeed a bitflip. At least we should report such unknown inode key types as errors. The lowmem mode is already doing such report, although not treating them as an error. The original mode just ignores them completely. So this patch enhance btrfs check to: - Report such unknown item and treat them as error for the original mode - Treat such unknown item as error for the lowmem mode Reported-by: mikkel+btrfs@mikkel.cc Link: https://lore.kernel.org/linux-btrfs/5d5e344e-96be-4436-9a58-d60ba14fdb4f@gmx.com/T/#me22cef92653e660e88a4c005b10f5201a8fd83ac Signed-off-by: Qu Wenruo --- check/main.c | 7 +++++++ check/mode-lowmem.c | 5 +++-- check/mode-lowmem.h | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/check/main.c b/check/main.c index db055ae19..96c7ef7d8 100644 --- a/check/main.c +++ b/check/main.c @@ -87,6 +87,7 @@ bool is_free_space_tree = false; bool init_extent_tree = false; bool check_data_csum = false; static bool found_free_ino_cache = false; +static bool found_unknown_key = false; struct cache_tree *roots_info_cache = NULL; enum btrfs_check_mode { @@ -1895,6 +1896,10 @@ static int process_one_leaf(struct btrfs_root *root, struct extent_buffer *eb, ret = process_xattr_item(eb, i, &key, active_node); break; default: + error("Unknown key (%llu %u %llu) found in leaf %llu", + key.objectid, key.type, key.offset, + eb->start); + found_unknown_key = true; break; }; } @@ -4027,6 +4032,8 @@ static int check_fs_roots(struct cache_tree *root_cache) free_extent_cache_tree(&wc.shared); if (!cache_tree_empty(&wc.shared)) fprintf(stderr, "warning line %d\n", __LINE__); + if (!err && found_unknown_key) + err = 1; return err; } diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c index ea4d40178..8a4c5bc1e 100644 --- a/check/mode-lowmem.c +++ b/check/mode-lowmem.c @@ -2808,8 +2808,9 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path) case BTRFS_XATTR_ITEM_KEY: break; default: - error("ITEM[%llu %u %llu] UNKNOWN TYPE", - key.objectid, key.type, key.offset); + error("ITEM[%llu %u %llu] UNKNOWN TYPE in leaf %llu", + key.objectid, key.type, key.offset, node->start); + err |= UNKNOWN_KEY; } } diff --git a/check/mode-lowmem.h b/check/mode-lowmem.h index cd0355465..ec199b4c8 100644 --- a/check/mode-lowmem.h +++ b/check/mode-lowmem.h @@ -48,6 +48,7 @@ #define INVALID_GENERATION (1U << 26) /* Generation is too new */ #define SUPER_BYTES_USED_ERROR (1U << 27) /* Super bytes_used is invalid */ #define DUP_FILENAME_ERROR (1U << 28) /* DIR_ITEM contains duplicate names */ +#define UNKNOWN_KEY (1U << 29) /* Found unknown key type in fs trees */ /* * Error bit for low memory mode check. From 4acdcb50a4713a55d941c3297beac5691affb4ab Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 16 Dec 2025 19:47:25 +1030 Subject: [PATCH 2/5] btrfs-progs: add a test case for unknown keys in subvolume trees The image has the following corrupted key in fs tree: item 4 key (257 INODE_ITEM 0) itemoff 15879 itemsize 160 generation 9 transid 9 size 0 nbytes 0 block group 0 mode 100644 links 1 uid 0 gid 0 rdev 0 sequence 1 flags 0x0(none) item 5 key (257 UNKNOWN.8 256) itemoff 15879 itemsize 0 <<< item 6 key (257 INODE_REF 256) itemoff 15863 itemsize 16 index 2 namelen 6 name: foobar This is inspired by a real world memory bitflip, which lead to a bitflip from 12 to 8, causing the above unknown key type in a subvolume. Although we will need to properly enhance btrfs-check to handle such case better, let's start from detecting and report such unknown keys as an error. The image is created by inserting an empty item with above unknown key type. Signed-off-by: Qu Wenruo --- tests/fsck-tests/069-unknown-fs-tree-key/test.sh | 14 ++++++++++++++ .../unknown_key_empty.img.xz | Bin 0 -> 2084 bytes 2 files changed, 14 insertions(+) create mode 100755 tests/fsck-tests/069-unknown-fs-tree-key/test.sh create mode 100644 tests/fsck-tests/069-unknown-fs-tree-key/unknown_key_empty.img.xz diff --git a/tests/fsck-tests/069-unknown-fs-tree-key/test.sh b/tests/fsck-tests/069-unknown-fs-tree-key/test.sh new file mode 100755 index 000000000..ea4f04e28 --- /dev/null +++ b/tests/fsck-tests/069-unknown-fs-tree-key/test.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Verify that check can report unknown key types in subvolume trees + +source "$TEST_TOP/common" || exit + +check_prereq btrfs + +check_image() { + run_mustfail "unknown keys in subvolume trees not reported as error" \ + "$TOP/btrfs" check "$1" +} + +check_all_images diff --git a/tests/fsck-tests/069-unknown-fs-tree-key/unknown_key_empty.img.xz b/tests/fsck-tests/069-unknown-fs-tree-key/unknown_key_empty.img.xz new file mode 100644 index 0000000000000000000000000000000000000000..300a7cc92b7125d0309be6917195695610a0f549 GIT binary patch literal 2084 zcmV+<2;2AlH+ooF000E$*0e?hz~m2rARz%3000000002T67Oc<5B~?;T>wRyj;C3^ zv%$$4d1t#phA1?^@KsLMx+L6Lndj_%x4-K}K}=vKHs(GA+TF>Mn@CK(n_L|D@0Kc1 z)xtakW67`9i=jvi2JoO=FXuia$6{k5uB4-JhhS}7(xV;_vbE!^@Ouxnalf^U(1X6z zU<+Zcg!1=9lnbcx&?a+K%qnFVlqH-JreY_lK~*yA2L8|9`DyI20j&`(dBgo*%!0<~Ocpwe0@E1v)1%)SCC`?aQC6Ht@U zE?|y%BIPkrsWLKE>Xz|#=`#g?0Jw2uf`=9QME;+`4Pjx97$HcA?R!`HkNJp0tq#0Tg3p8XMh{5c(cE1BPDdG{i#TzdIKlYOfsBVp~jSYcZm$ zWGbfN!)uNdv+m{Caz-`>#n>0rLI(~_9qLPoZX{W6C*D+=B=GPuC^9Hpg<6~S-szbYRg;6&x7U9M zOs2c7@A6CYi^4o3D;;;HpkYX1pK;8Q%U@RNRWFZ!|Nf3pvWtdlt6{MxS+C|9kmX!s zP7YR&BdeO`-2WTFS2fV&NEXA@NZqidjaD;pC51ENMqT~fU8%?t-mm`ZDuBJvs;#Du z&X?PPq_slz3KxE6^@=HYg-|(hS8z&=4TNj%18yO^u}@fn!^UJK*Xvl75C|7gEVLMZ zEA*Q6nSuL;X2(>{VN!pZ;_uOp*!^v3nVI!o7|iM)5wa*&%v`L+=?Nz&5Msil>A}$y zle8a7uAqC-GJp)V>$~-CA1v5kJ=~$$lAr?SR@uQ zesl|58%RpoZCAs#I}c$x7gtOnay1-eR<`FZKq&TmY?@Uu*^D{C$bWAb2Lr~4R3FW! zi1Zno=($u1oaKV%;M`%2QOalAp8;A#SS5E%fQR+`Ao=MC^)cy_oHmR(YZnv@EA;n* z#MVxq!L=asCt=pe;Oo5yPtY{WD%`>ODF<0MNvy%R;hjXfy%J59Mv&YQAaPGA7j}eW zlV3DUMmKOP`bTrZJX*;HhgmsshW~eJD{*Qn$@n9rV$~)$v%RdKOVdLdP1<`MTorM< z&Cpd}q`}T(*-OFSN$v2MX&Tx#+d53giKR|%=6uuUFvvsrz?==Ai5mC48K4jM%J4lxnfWk zE1Ag97wTeXiRLMi`eMP4CIS74s7Kr{^mf?gfh<;pZ}kwtHwi)Yy$>Xg;x-lqanC{?o4+tI+HTW51PKJ#7Dc*vywyeHb4JCq zONf+^pb((if}a(MHCdOXSGnpnsx25k+%F(s6r z45&stsxDJg-PdojbQBQ&Z`gTDhmH=H42AOL5=uPcexG!J zvkQ#g(2jkvIDSW60WS+hE>gLC+j<=_5N!(r{ufowj&diXtlqWBK4DQqK-5(fs``ym zyoN6=?@U57dc(XyjwsunpO0*=$CrRQ9c_ILjIg1kSN?Pee%WgKZA(T% zG`GN3{!gl$2mra}e4vLZljY?EK3;sC)w|kf-VnMu1VWZ=4CA?f5qd3P6O^69?oTLk z2+|M0-`j8ZgL`oe;nFy4EgPk_*_cfs=5~?JD|Vaeibq#jpN|oi_^>7XRQ3K^(g>Lu!p6$VayGx-1*qRZ^AQoRALDINyMSIc`3+k~ttk@gsP0Lq^h;Ti?BF&- z9sOeu&b@D8jsP_;wdSJJZhd}%?`Cp9C&cy>cmM!lAy=SVdh0p>0e}#IAOHYdrx9qe O#Ao{g000001X)^#E(fLn literal 0 HcmV?d00001 From f918d7325772029501db8fc57d606cbe7450b41e Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 17 Dec 2025 14:38:24 +1030 Subject: [PATCH 3/5] btrfs-progs: check/original: add dedicated missing INODE_REF repair Currently if the original mode of btrfs-check hits a missing INODE_REF, but with valid DIR_INDEX/DIR_ITEM, then it will repair it by deleting the valid DIR_INDEX, which will just make things worse. Add a dedicated repair for missing INODE_REF which will just add back the missing INODE_REF, properly fixing the problem other than making it worse. Signed-off-by: Qu Wenruo --- check/main.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/check/main.c b/check/main.c index 96c7ef7d8..880827e11 100644 --- a/check/main.c +++ b/check/main.c @@ -2351,6 +2351,42 @@ static int create_inode_item(struct btrfs_root *root, return 0; } +static int create_inode_ref(struct btrfs_root *root, + struct inode_record *i_rec, + struct inode_backref *backref) +{ + struct btrfs_trans_handle *trans; + int ret; + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + errno = -ret; + error_msg(ERROR_MSG_START_TRANS, "%m"); + return ret; + } + + ret = btrfs_insert_inode_ref(trans, root, backref->name, backref->namelen, + i_rec->ino, backref->dir, backref->index); + if (ret < 0) { + btrfs_commit_transaction(trans, root); + return ret; + } + ret = btrfs_commit_transaction(trans, root); + if (ret < 0) { + ret = PTR_ERR(trans); + errno = -ret; + error_msg(ERROR_MSG_COMMIT_TRANS, "%m"); + return ret; + } + backref->found_inode_ref = 1; + backref->errors &= ~REF_ERR_NO_INODE_REF; + printf("Added INODE_REF for root %lld ino %llu parent %llu index %llu name %.*s\n", + btrfs_root_id(root), i_rec->ino, backref->dir, backref->index, + backref->namelen, backref->name); + return 0; +} + static int repair_inode_backrefs(struct btrfs_root *root, struct inode_record *rec, struct cache_tree *inode_cache, @@ -2375,6 +2411,21 @@ static int repair_inode_backrefs(struct btrfs_root *root, if (rec->ino == root_dirid && backref->index == 0) continue; + /* + * Have DIR_INDEX, DIR_ITEM and INODE_ITEM, and even nlinks + * matches with only missing INODE_REF. + */ + if (!backref->found_inode_ref && backref->found_dir_item && + backref->found_dir_index && rec->found_inode_item && + rec->found_link == rec->nlink) { + ret = create_inode_ref(root, rec, backref); + if (ret) + break; + repaired++; + list_del(&backref->list); + free(backref); + continue; + } if (delete && ((backref->found_dir_index && !backref->found_inode_ref) || (backref->found_dir_index && backref->found_inode_ref && From c634cd92dd6c6da05dc1be1444f1c98f85d9c4f6 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 17 Dec 2025 16:14:26 +1030 Subject: [PATCH 4/5] btrfs-progs: check/lowmem: fix INODE_REF repair [BUG] Although lowmem has the logical to repair one missing INODE_REF/DIR_INDEX/DIR_ITEM, there is a catch in missing INODE_REF. If we're checking an DIR_ITEM (which we normally hits first), and failed to find the INODE_REF, we are in a situation where there is no @index to delete the original DIR_INDEX/DIR_ITEM pair. This can cause further damage to the fs, without really improving anything. [CAUSE] There is a minimal example where there is missing INODE_ITEM: item 0 key (256 INODE_ITEM 0) itemoff 16123 itemsize 160 generation 3 transid 9 size 12 nbytes 16384 block group 0 mode 40755 links 1 uid 0 gid 0 rdev 0 sequence 1 flags 0x0(none) item 1 key (256 INODE_REF 256) itemoff 16111 itemsize 12 index 0 namelen 2 name: .. item 2 key (256 DIR_ITEM 496027801) itemoff 16075 itemsize 36 location key (257 INODE_ITEM 0) type FILE transid 9 data_len 0 name_len 6 name: foobar item 3 key (256 DIR_INDEX 2) itemoff 16039 itemsize 36 location key (257 INODE_ITEM 0) type FILE transid 9 data_len 0 name_len 6 name: foobar item 4 key (257 INODE_ITEM 0) itemoff 15879 itemsize 160 generation 9 transid 9 size 0 nbytes 0 block group 0 mode 100644 links 1 uid 0 gid 0 rdev 0 sequence 1 flags 0x0(none) For above case, if we're check the DIR_ITEM, we can find the INODE_ITEM but not the INODE_REF. So we need to repair it, but at this stage we haven't checked DIR_INDEX, this means we have no idea which index we should delete, leaving the default @index as (-1). If we call btrfs_unlink() with that (-1) as index, it will not delete the (256 DIR_INDEX 2) one, then re-add a link using -1 as index, causing more damage. [FIX] Before calling btrfs_unlink(), do an extra check on if we're called from DIR_ITEM checks (aka, @index is -1) and we only detected a missing INODE_REF yet. If so, do find_dir_index() call to determine the DIR_INDEX, if that failed it means we have missing both DIR_INDEX and INODE_REF, thus should remove the lone DIR_ITEM instead. With this enhancement, lowmem mode can properly fix the missing INODE_REF by adding it back. Signed-off-by: Qu Wenruo --- check/mode-lowmem.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c index 8a4c5bc1e..dcec6cc06 100644 --- a/check/mode-lowmem.c +++ b/check/mode-lowmem.c @@ -1015,6 +1015,17 @@ static int repair_ternary_lowmem(struct btrfs_root *root, u64 dir_ino, u64 ino, int stage = 0; int ret = 0; + /* + * We miss an INODE_REF, and we're checking DIR_ITEM and hasn't yet + * find the DIR_INDEX, thus there is no reliable index. + * Try to locate one, this can be slow as we need to locate the DIR_INDEX + * item from the directory. + */ + if (index == (u64)-1 && (err & INODE_REF_MISSING)) { + ret = find_dir_index(root, dir_ino, ino, &index, name, name_len, filetype); + if (ret < 0) + err |= DIR_INDEX_MISSING; + } /* * stage shall be one of following valild values: * 0: Fine, nothing to do. From 2174d23a6cf83e392fb64a1b9367da60ea1470fe Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 17 Dec 2025 16:26:41 +1030 Subject: [PATCH 5/5] btrfs-progs: fsck-tests: add a test case for repairing missing INODE_REF The image is created by removing the INODE_REF for inode 257. Now both original and lowmem mode should be able to repair it. Signed-off-by: Qu Wenruo --- .../070-missing-inode-ref/.lowmem_repairable | 0 .../070-missing-inode-ref/default.img.xz | Bin 0 -> 2092 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/fsck-tests/070-missing-inode-ref/.lowmem_repairable create mode 100644 tests/fsck-tests/070-missing-inode-ref/default.img.xz diff --git a/tests/fsck-tests/070-missing-inode-ref/.lowmem_repairable b/tests/fsck-tests/070-missing-inode-ref/.lowmem_repairable new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fsck-tests/070-missing-inode-ref/default.img.xz b/tests/fsck-tests/070-missing-inode-ref/default.img.xz new file mode 100644 index 0000000000000000000000000000000000000000..15d95eed391c14630d93be2bf3e665186f816259 GIT binary patch literal 2092 zcmV+{2-EldH+ooF000E$*0e?h!08WwARz%3000000001-VinQg5B~?@T>wRyj;C3^ zv%$$4d1t#phBKh;&NC{9&(d!b2E59M;JC0UU%kWL^X+sZVfYN*5%p2^>6}9|3)>Dn zXewlH=cH%u0tV-aU}q-cf_=~UV?j3QP8f*N@R>AHVT#v<_x$Cpp`eu@pQKB55$aa!T% zQC$g}3u%YxS{(Qf#7<|6f|-l}&y<;iP7}n=RAT?+A3yBpPgwB9L*8OJK!(oX_@7aa%exU;SN7w?7a>#O)ZV8S}T zn2D|A28YOCL|;Cr**6EY3{d*@3IPjbD5$a}=!n%6mm8Zk7ee#%9OHZ|gvn*wcX# z$o{8$*1yB>L>HqW`+w83_K7%+KK z`)a;>Ru5v!w@!ocNPD?4WmeRTdc&mRZWPD0p9B zI4Hm*vJ}(Aq(Q_XrBh$y3@T=%b3!G;F{hxXnqna7%~d|ExPYqod-J8iCh<#}6s*4v z*N|gn$zIJiJtr2=TD`LLW|k{vn4zV1nwRB3dstc^!BHccF2EZrcmp5c(0NjqWU!<= z(WvyJ!rv#nU&H>C5)T8&vr3Rp7>%o>7Lq=pGj)SfqzR9coi8@xL$p81Yld7B65*A# z3upSm5jwuPI*mVbH}NE@>s_fT1&%~bzA8$c;*3MbYf%H9Rd!_A<-h1oD`5RsJh^#E zff0+sF}C2W%v*CKC+v(GSkG-vKb26@i$qm-F1}F))dWv!1lkpo_n{XK&B0hSXfU#$ zs1Pa0s+0HWFp^L6R#@Ii_H4|@8OzBofafOC`S9i%3!OG|KR! zt<~Dx5ETIwud=t?5vsMgZJik9Jgq%H>0gAUoUZVg&z6CqG!j|g53n7oFtDVSG+^o= zYuz2d>eg5K7tW%41B}!6EHXO8a{gR!5hsrp_)M0!tG74Ky={DU@2tD$PiTBfJ+pV@ zWv*`Q(L?3zWd+Z5M8A1FL0Bn(V)YDCx4q9dTYp!BMH_sFdC#fi^QnB6@!Pg;0uIwB zZr7kK+gxCCV>eEEGm%*=5C-31`B@Q zhR@1r8(?H?9aCn<*b;X!2o{remf=!0WOP^JMx)iNlKvsEH|}CKT{MBoscRjOM!Lbn z83G!8Sef_^3KL6DZSNqo9BidQ`Nt1Il#X{*JKRD*E%pn-x9 z`btWC|FM%1_wXkQ9DZ{47hc5ohg(c{Ea=z9;f&jSUsr+QDLTE|>A&)X`@lMNHc82g z>+u*;^YIHR4Hykg?549M&}gL>7enZH1rRs3w{lH5N7+L(H~uGY<6^|mEP9S!=i=)w z5l@GRnEaA7J7QeHRE-uwQ?^e#`J5G)Yr6}zS+AcUG@YPNuw-C|6P3MjNlS#t$zK?p zJh_!RK)DKescpx{&~gN8XYY}=?2m++w%32K&c7V7)nTexab=K`Kz-l(JGsfJ=n4De zGC;|3ex6mD&79VWll2cTTL#$B%ZUKg)#PmH~|m6#`B9yl)L9()qmk-fPb`J37dfkJw+dl+cij` zR~+~hCcMLq6n8iI-*z9}S^BLBQSzMHYSX0vy>H(yy&`kAtO6@ry7a-DXCu;5!#H5? z(-2+NU(YuWnx`NrIlJbzJkmh8p`dhcX`k8{jV>R!{)zwq0001K(3CMq;;cXb0fi8N WAOHaGHrzO|#Ao{g000001X)@P;SOd1 literal 0 HcmV?d00001