[PATCH e2fsprogs 2/2] tests: add d_rdump_safe for the debugfs rdump path-traversal guard
Nexory <[email protected]> Fri, 7 Aug 2026 12:03:04 +0200
| Newsgroups | org.kernel.vger.linux-ext4 |
|---|---|
| Message-ID | <OS6P279MB1180BE9B7C39128DD2B6D891CBD12@OS6P279MB1180.NORP279.PROD.OUTLOOK.COM> |
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 <[email protected]> --- 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