* [PATCH e2fsprogs 0/2] debugfs: fix rdump path-traversal on untrusted images
@ 2026-08-07 10:03 Nexory
2026-08-07 10:03 ` [PATCH e2fsprogs 1/2] debugfs: reject path-traversal names in rdump Nexory
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Nexory @ 2026-08-07 10:03 UTC (permalink / raw)
To: linux-ext4; +Cc: Theodore Ts'o, Nexory
debugfs "rdump" extracts a directory tree from a filesystem image to a
destination directory. It copies each raw on-disk directory entry name into
the output path without checking it, so an entry whose name contains a '/'
(for example "../escaped") in a crafted or corrupted image is written outside
the destination directory. A valid ext2/3/4 filesystem never contains such a
name, and e2fsck already flags it as corruption (PR_2_BAD_NAME), so only
untrusted or damaged images are affected, which is exactly what debugfs is
pointed at.
Patch 1 adds the guard in rdump_dirent(). Patch 2 adds a regression test,
d_rdump_safe, which fails without patch 1 (the entry escapes) and passes with
it, while still dumping the legitimate file unchanged.
The test needs a directory entry whose name contains a '/', which no standard
tool will create, so it ships a small crafted image the same way the f_* fsck
tests do; tests/d_rdump_safe/mkimage.sh documents exactly how it was built.
After applying the series the binary fixture should be intact:
sha256sum tests/d_rdump_safe/image.gz
e304c8fe87863da197d2565ca95599c5dca2fbd22640aefcf70258d6935eb922 (922 bytes)
git show --stat HEAD # patch 2 adds 5 files under tests/d_rdump_safe/
If that hash differs or files are missing, the binary block was mangled in
transit; please ask for a resend.
Happy to adjust the com_err wording or the guard's placement if you prefer.
Nexory (2):
debugfs: reject path-traversal names in rdump
tests: add d_rdump_safe for the debugfs rdump path-traversal guard
debugfs/dump.c | 10 +++++++
tests/d_rdump_safe/expect | 6 ++++
tests/d_rdump_safe/image.gz | Bin 0 -> 922 bytes
tests/d_rdump_safe/mkimage.sh | 55 ++++++++++++++++++++++++++++++++++
tests/d_rdump_safe/name | 1 +
tests/d_rdump_safe/script | 22 ++++++++++++++
6 files changed, 94 insertions(+)
create mode 100644 tests/d_rdump_safe/expect
create mode 100644 tests/d_rdump_safe/image.gz
create mode 100755 tests/d_rdump_safe/mkimage.sh
create mode 100644 tests/d_rdump_safe/name
create mode 100644 tests/d_rdump_safe/script
base-commit: 43643a57fb2d3368fbacd181a8cd713102d52a1a
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH e2fsprogs 1/2] debugfs: reject path-traversal names in rdump
2026-08-07 10:03 [PATCH e2fsprogs 0/2] debugfs: fix rdump path-traversal on untrusted images Nexory
@ 2026-08-07 10:03 ` Nexory
2026-08-07 10:03 ` [PATCH e2fsprogs 2/2] tests: add d_rdump_safe for the debugfs rdump path-traversal guard Nexory
2026-10-05 9:01 ` [PATCH e2fsprogs 0/2] debugfs: fix rdump path-traversal on untrusted images Theodore Tso
2 siblings, 0 replies; 4+ messages in thread
From: Nexory @ 2026-08-07 10:03 UTC (permalink / raw)
To: linux-ext4; +Cc: Theodore Ts'o, Nexory
rdump_dirent() copies the raw on-disk directory entry name and passes it to
rdump_inode(), which sprintf()s it into the destination path and open()s /
symlink()s the result. An entry named "../foo" (or one containing '/') in a
crafted or corrupted image therefore escapes the destination directory when
"rdump" extracts a tree from an untrusted filesystem image.
Reject such names in rdump_dirent() before use. Valid ext2/3/4 filesystems
never contain them, so there is no change for legitimate images; e2fsck
already treats '/' in a name as corruption (PR_2_BAD_NAME).
Signed-off-by: Nexory <St4yl3r30@hotmail.de>
---
debugfs/dump.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/debugfs/dump.c b/debugfs/dump.c
index cc81a50..e959cd0 100644
--- a/debugfs/dump.c
+++ b/debugfs/dump.c
@@ -317,6 +317,16 @@ static int rdump_dirent(struct ext2_dir_entry *dirent,
strncpy(name, dirent->name, thislen);
name[thislen] = 0;
+ /* Path-traversal guard: reject entry names that would escape the
+ * destination directory. A valid ext2/3/4 filesystem never contains
+ * a name with '/' or an empty name; e2fsck treats such names as
+ * corruption (PR_2_BAD_NAME). */
+ if (name[0] == 0 || strchr(name, '/')) {
+ com_err("rdump", 0, "skipping entry with unsafe name (inode %u)",
+ dirent->inode);
+ return 0;
+ }
+
if (debugfs_read_inode(dirent->inode, &inode, name))
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH e2fsprogs 2/2] tests: add d_rdump_safe for the debugfs rdump path-traversal guard
2026-08-07 10:03 [PATCH e2fsprogs 0/2] debugfs: fix rdump path-traversal on untrusted images Nexory
2026-08-07 10:03 ` [PATCH e2fsprogs 1/2] debugfs: reject path-traversal names in rdump Nexory
@ 2026-08-07 10:03 ` Nexory
2026-10-05 9:01 ` [PATCH e2fsprogs 0/2] debugfs: fix rdump path-traversal on untrusted images Theodore Tso
2 siblings, 0 replies; 4+ messages in thread
From: Nexory @ 2026-08-07 10:03 UTC (permalink / raw)
To: linux-ext4; +Cc: Theodore Ts'o, Nexory
Extracts a crafted image whose root directory contains a normal file and a
hostile "../escaped" hard link to it. The test confirms rdump skips the
unsafe name, does not create a file outside the destination, and still dumps
the legitimate file. Without the guard the test fails (the entry escapes the
destination directory).
The malicious name contains a '/', which a valid filesystem never has and
which mke2fs/debugfs will not create, so the image is built with a small
libext2fs helper; mkimage.sh in the test directory documents the recipe.
Signed-off-by: Nexory <St4yl3r30@hotmail.de>
---
tests/d_rdump_safe/expect | 6 ++++
tests/d_rdump_safe/image.gz | Bin 0 -> 922 bytes
tests/d_rdump_safe/mkimage.sh | 55 ++++++++++++++++++++++++++++++++++
tests/d_rdump_safe/name | 1 +
tests/d_rdump_safe/script | 22 ++++++++++++++
5 files changed, 84 insertions(+)
create mode 100644 tests/d_rdump_safe/expect
create mode 100644 tests/d_rdump_safe/image.gz
create mode 100755 tests/d_rdump_safe/mkimage.sh
create mode 100644 tests/d_rdump_safe/name
create mode 100644 tests/d_rdump_safe/script
diff --git a/tests/d_rdump_safe/expect b/tests/d_rdump_safe/expect
new file mode 100644
index 0000000..fd8ed61
--- /dev/null
+++ b/tests/d_rdump_safe/expect
@@ -0,0 +1,6 @@
+debugfs rdump path-traversal guard
+debugfs -R ''rdump / DEST/out'' image
+rdump: skipping entry with unsafe name (inode NN)
+Exit status is 0
+escaped outside destination: no
+legitimate file dumped: yes
diff --git a/tests/d_rdump_safe/image.gz b/tests/d_rdump_safe/image.gz
new file mode 100644
index 0000000000000000000000000000000000000000..dd0e617255477017429477b603a409d382ac1f0a
GIT binary patch
literal 922
zcmb2|=3sdFwj_&*`R$$k*}{nu>>s|rdo;<7<)i!7e;s~JW>zBN(@Tm)SjsaWE2xIQ
z@Dyj4jfr{l;DtuVx~A^##s6D&a~xed+qp)6dR7p3Q^wOB=hba?Rv(T%A^WrKx$2!(
z29wbJHD1mZqT=%>2~2xrH1pnq=+JtjKYL>D@F$w=`fDngAKt6I`}bL2w&|<3^F^*%
zo1Pyp9~<KrAG6c+{{GL;PX4#JwtV($Zfo<ry2|hW9)5nkeqZji`P+?)W9q}r?IyL=
z^!IfK=}x_wbJWV@-2Y1!>m7`H7QH!NUT?zm>h+29_Zb&N9=h_UeEX~`|8w-ig%`y4
z+s~fK{de`x-174~GM~#cSX9WL`uF@+-CuvHXUn%)3%*WS^J(j+{fjPd6ib@hS!#A|
zXT)>$zh_#dzS`@{w-;30|7Sk)<$t|b{~3@#19Mp1<13t=w|;M&w`^}*DgXZDg>%<E
zl|K5k`e*aQtkU&+{4!LtE90iTmfu`<>2<7Z&YC;1@@uT72bdREf7(Ae^e4O2qNgGa
wf0#e<pRiZ>kIJ~>SgCvdk(Zqg+u=oTOOUJ`We%$l_;aoNSlGgy3=A9$01}hoO8@`>
literal 0
HcmV?d00001
diff --git a/tests/d_rdump_safe/mkimage.sh b/tests/d_rdump_safe/mkimage.sh
new file mode 100755
index 0000000..817f052
--- /dev/null
+++ b/tests/d_rdump_safe/mkimage.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# This is the script that was used to create the image.gz in this directory.
+#
+# The image is a tiny ext2 filesystem whose root directory holds a normal file
+# "loot" and a second, hostile directory entry whose name is the literal string
+# "../escaped" (a hard link to loot's inode). A valid filesystem never contains
+# a name with a '/' in it, and neither mke2fs nor debugfs will create one, so the
+# hostile entry is planted with a small libext2fs helper: ext2fs_link() writes
+# the raw name without validating it.
+#
+# "debugfs -R 'rdump / <dest>'" used to copy that entry to <dest>/../escaped,
+# i.e. outside <dest>. See the d_rdump_safe test.
+#
+# Requires mke2fs, debugfs and the libext2fs headers (the e2fsprogs build tree,
+# or the e2fslibs-dev / libext2fs-dev package). The UUID and timestamps are not
+# pinned, so a re-run yields a functionally identical but not byte-identical
+# image, the same way the f_* fsck test images do.
+
+set -e -u
+
+IMG=image
+
+# 1. A tiny ext2 filesystem: 1024-byte blocks, 512 blocks, 64 inodes.
+mke2fs -F -q -b 1024 -N 64 "$IMG" 512
+
+# 2. A normal file "loot" in the root directory.
+printf 'loot-content\n' > loot-content
+debugfs -w -R "write loot-content loot" "$IMG"
+rm -f loot-content
+
+# 3. Plant a hostile hard link named "../escaped" onto loot's inode.
+cat > plant.c <<'PLANT'
+#include <ext2fs/ext2fs.h>
+
+int main(int argc, char **argv)
+{
+ ext2_filsys fs;
+ ext2_ino_t ino;
+
+ if (ext2fs_open(argv[1], EXT2_FLAG_RW, 0, 0, unix_io_manager, &fs))
+ return 1;
+ if (ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, "loot", &ino))
+ return 1;
+ if (ext2fs_link(fs, EXT2_ROOT_INO, "../escaped", ino, EXT2_FT_REG_FILE))
+ return 1;
+ return ext2fs_close(fs) ? 1 : 0;
+}
+PLANT
+cc -o plant plant.c -lext2fs -lcom_err
+./plant "$IMG"
+rm -f plant plant.c
+
+# 4. Compress; the result is committed as image.gz.
+gzip -9 -f "$IMG"
diff --git a/tests/d_rdump_safe/name b/tests/d_rdump_safe/name
new file mode 100644
index 0000000..00d7a7e
--- /dev/null
+++ b/tests/d_rdump_safe/name
@@ -0,0 +1 @@
+debugfs rdump path-traversal guard
diff --git a/tests/d_rdump_safe/script b/tests/d_rdump_safe/script
new file mode 100644
index 0000000..f06637d
--- /dev/null
+++ b/tests/d_rdump_safe/script
@@ -0,0 +1,22 @@
+if ! test -x $DEBUGFS_EXE; then
+ echo "$test_name: $test_description: skipped (no debugfs)"
+ return 0
+fi
+OUT=$test_name.log
+EXP=$test_dir/expect
+echo "debugfs rdump path-traversal guard" > $OUT.new
+gunzip < $test_dir/image.gz > $TMPFILE
+DEST=$test_name.d
+rm -rf $DEST; mkdir -p $DEST/out
+echo "debugfs -R ''rdump / DEST/out'' image" >> $OUT.new
+$DEBUGFS -R "rdump / `pwd`/$DEST/out" $TMPFILE >> $OUT.new 2>&1
+echo "Exit status is $?" >> $OUT.new
+if test -e $DEST/escaped ; then echo "escaped outside destination: yes (VULNERABLE)" >> $OUT.new ; else echo "escaped outside destination: no" >> $OUT.new ; fi
+if test -e $DEST/out/loot ; then echo "legitimate file dumped: yes" >> $OUT.new ; else echo "legitimate file dumped: no" >> $OUT.new ; fi
+sed -f $cmd_dir/filter.sed $OUT.new | sed -e "s;`pwd`/;;g" -e "s/inode [0-9]*/inode NN/" > $OUT
+rm -rf $DEST $TMPFILE $OUT.new
+cmp -s $OUT $EXP
+status=$?
+if [ "$status" = 0 ] ; then echo "$test_name: $test_description: ok"; touch $test_name.ok
+else echo "$test_name: $test_description: failed"; diff $DIFF_OPTS $EXP $OUT > $test_name.failed; fi
+unset OUT EXP DEST
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH e2fsprogs 0/2] debugfs: fix rdump path-traversal on untrusted images
2026-08-07 10:03 [PATCH e2fsprogs 0/2] debugfs: fix rdump path-traversal on untrusted images Nexory
2026-08-07 10:03 ` [PATCH e2fsprogs 1/2] debugfs: reject path-traversal names in rdump Nexory
2026-08-07 10:03 ` [PATCH e2fsprogs 2/2] tests: add d_rdump_safe for the debugfs rdump path-traversal guard Nexory
@ 2026-10-05 9:01 ` Theodore Tso
2 siblings, 0 replies; 4+ messages in thread
From: Theodore Tso @ 2026-10-05 9:01 UTC (permalink / raw)
To: Nexory; +Cc: linux-ext4
On Fri, Aug 07, 2026 at 12:03:02PM -0500, Nexory wrote:
> debugfs "rdump" extracts a directory tree from a filesystem image to a
> destination directory. It copies each raw on-disk directory entry name into
> the output path without checking it, so an entry whose name contains a '/'
> (for example "../escaped") in a crafted or corrupted image is written outside
> the destination directory. A valid ext2/3/4 filesystem never contains such a
> name, and e2fsck already flags it as corruption (PR_2_BAD_NAME), so only
> untrusted or damaged images are affected, which is exactly what debugfs is
> pointed at.
Sorry, I forgot to send an acknowledgement of this when I pushed the
change out earlier, but thanks, I've applied your changes.
- Ted
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-05 9:01 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07 10:03 [PATCH e2fsprogs 0/2] debugfs: fix rdump path-traversal on untrusted images Nexory
2026-08-07 10:03 ` [PATCH e2fsprogs 1/2] debugfs: reject path-traversal names in rdump Nexory
2026-08-07 10:03 ` [PATCH e2fsprogs 2/2] tests: add d_rdump_safe for the debugfs rdump path-traversal guard Nexory
2026-10-05 9:01 ` [PATCH e2fsprogs 0/2] debugfs: fix rdump path-traversal on untrusted images Theodore Tso
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox