All of lore.kernel.org
 help / color / mirror / Atom feed
* [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 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.