[PATCH] erofs-utils: tests: add test for malformed tar hardlink targets
Vansh Choudhary
ch at vnsh.in
Sat Mar 28 06:37:12 AEDT 2026
Add a regression test for malformed tar hardlink targets.
Signed-off-by: Vansh Choudhary <ch at vnsh.in>
---
tests/Makefile.am | 3 +
tests/erofs/029 | 54 ++++++++++++++
tests/erofs/029.out | 2 +
tests/src/Makefile.am | 3 +
tests/src/badtar.c | 159 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 221 insertions(+)
create mode 100755 tests/erofs/029
create mode 100644 tests/erofs/029.out
create mode 100644 tests/src/badtar.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index e376d6a..1e9726f 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -122,6 +122,9 @@ TESTS += erofs/027
# 028 - test inode page cache sharing functionality
TESTS += erofs/028
+# 029 - regression test for malformed tar hardlink targets
+TESTS += erofs/029
+
EXTRA_DIST = common/rc erofs
clean-local: clean-local-check
diff --git a/tests/erofs/029 b/tests/erofs/029
new file mode 100755
index 0000000..cd1a8d3
--- /dev/null
+++ b/tests/erofs/029
@@ -0,0 +1,54 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0+
+#
+# 029 - check malformed tar hardlink targets
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$(echo $0 | awk '{print $((NF-1))"/"$NF}' FS="/")
+
+# get standard environment, filters and checks
+. "${srcdir}/common/rc"
+
+cleanup()
+{
+ cd /
+ rm -rf $tmp.*
+}
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+echo "QA output created by $seq"
+
+if [ -z $SCRATCH_DEV ]; then
+ SCRATCH_DEV=$tmp/erofs_$seq.img
+ rm -f SCRATCH_DEV
+fi
+
+localdir="$tmp/$seq"
+rm -rf $localdir
+mkdir -p $localdir
+
+${PWD}/src/badtar $localdir >> $seqres.full 2>&1 || \
+ _fail "failed to prepare tarballs"
+
+rm -f $SCRATCH_DEV
+$MKFS_EROFS_PROG --tar=f $SCRATCH_DEV $localdir/ok.tar \
+ >> $seqres.full 2>&1 || _fail "valid tar hardlink failed unexpectedly"
+
+for t in empty dot; do
+ rm -f $SCRATCH_DEV
+ $MKFS_EROFS_PROG --tar=f $SCRATCH_DEV $localdir/$t.tar \
+ >> $seqres.full 2>&1
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ _fail "malformed tar with $t hardlink target passed unexpectedly"
+ elif [ $ret -gt 128 ]; then
+ _fail "mkfs crashed on malformed tar with $t hardlink target"
+ fi
+done
+
+echo Silence is golden
+status=0
+exit 0
diff --git a/tests/erofs/029.out b/tests/erofs/029.out
new file mode 100644
index 0000000..8ee6db4
--- /dev/null
+++ b/tests/erofs/029.out
@@ -0,0 +1,2 @@
+QA output created by 029
+Silence is golden
diff --git a/tests/src/Makefile.am b/tests/src/Makefile.am
index 16de41a..4a02509 100644
--- a/tests/src/Makefile.am
+++ b/tests/src/Makefile.am
@@ -15,3 +15,6 @@ badlz4_SOURCES = badlz4.c
badlz4_CFLAGS = ${liblz4_CFLAGS}
badlz4_LDADD = ${liblz4_LIBS}
endif
+
+check_PROGRAMS += badtar
+badtar_SOURCES = badtar.c
diff --git a/tests/src/badtar.c b/tests/src/badtar.c
new file mode 100644
index 0000000..77b20af
--- /dev/null
+++ b/tests/src/badtar.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * erofs-utils/tests/src/badtar.c
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+struct tar_header {
+ char name[100];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char size[12];
+ char mtime[12];
+ char chksum[8];
+ char typeflag;
+ char linkname[100];
+ char magic[6];
+ char version[2];
+ char uname[32];
+ char gname[32];
+ char devmajor[8];
+ char devminor[8];
+ char prefix[155];
+ char padding[12];
+};
+
+static int writeall(int fd, const void *buf, size_t len)
+{
+ const char *p = buf;
+
+ while (len) {
+ ssize_t ret = write(fd, p, len);
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ return -errno;
+ }
+ p += ret;
+ len -= ret;
+ }
+ return 0;
+}
+
+static void tar_octal(char *buf, size_t size, unsigned long long value)
+{
+ snprintf(buf, size, "%0*llo", (int)size - 1, value);
+}
+
+static void tar_checksum(struct tar_header *h)
+{
+ unsigned int i, sum = 0;
+ const unsigned char *p = (const unsigned char *)h;
+
+ memset(h->chksum, ' ', sizeof(h->chksum));
+ for (i = 0; i < sizeof(*h); ++i)
+ sum += p[i];
+ snprintf(h->chksum, sizeof(h->chksum), "%06o", sum);
+ h->chksum[6] = '\0';
+ h->chksum[7] = ' ';
+}
+
+static void tar_fill_header(struct tar_header *h, const char *name,
+ char typeflag, const char *linkname,
+ unsigned int mode, unsigned int size)
+{
+ memset(h, 0, sizeof(*h));
+ snprintf(h->name, sizeof(h->name), "%s", name);
+ tar_octal(h->mode, sizeof(h->mode), mode);
+ tar_octal(h->uid, sizeof(h->uid), 0);
+ tar_octal(h->gid, sizeof(h->gid), 0);
+ tar_octal(h->size, sizeof(h->size), size);
+ tar_octal(h->mtime, sizeof(h->mtime), 0);
+ h->typeflag = typeflag;
+ if (linkname)
+ snprintf(h->linkname, sizeof(h->linkname), "%s", linkname);
+ memcpy(h->magic, "ustar", 5);
+ memcpy(h->version, "00", 2);
+ memcpy(h->uname, "root", 4);
+ memcpy(h->gname, "root", 4);
+ tar_checksum(h);
+}
+
+static int write_tar(const char *path, const char *linkname)
+{
+ static const char zeros[512];
+ struct tar_header h;
+ int fd, ret;
+
+ fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ if (fd < 0)
+ return -errno;
+
+ tar_fill_header(&h, "a", '0', NULL, 0644, 511);
+ ret = writeall(fd, &h, sizeof(h));
+ if (ret)
+ goto out;
+ ret = writeall(fd, zeros, 511);
+ if (ret)
+ goto out;
+ ret = writeall(fd, zeros, 1);
+ if (ret)
+ goto out;
+
+ tar_fill_header(&h, "bad", '1', linkname, 0644, 0);
+ ret = writeall(fd, &h, sizeof(h));
+ if (ret)
+ goto out;
+ ret = writeall(fd, zeros, sizeof(zeros));
+ if (ret)
+ goto out;
+ ret = writeall(fd, zeros, sizeof(zeros));
+out:
+ close(fd);
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ char path[PATH_MAX];
+ int ret;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <dir>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ ret = snprintf(path, sizeof(path), "%s/ok.tar", argv[1]);
+ if (ret < 0 || ret >= sizeof(path))
+ return EXIT_FAILURE;
+ ret = write_tar(path, "a");
+ if (ret)
+ goto err;
+
+ ret = snprintf(path, sizeof(path), "%s/empty.tar", argv[1]);
+ if (ret < 0 || ret >= sizeof(path))
+ return EXIT_FAILURE;
+ ret = write_tar(path, "");
+ if (ret)
+ goto err;
+
+ ret = snprintf(path, sizeof(path), "%s/dot.tar", argv[1]);
+ if (ret < 0 || ret >= sizeof(path))
+ return EXIT_FAILURE;
+ ret = write_tar(path, ".");
+ if (ret)
+ goto err;
+ return EXIT_SUCCESS;
+err:
+ fprintf(stderr, "failed to create tarballs: %s\n", strerror(-ret));
+ return EXIT_FAILURE;
+}
--
2.43.0
More information about the Linux-erofs
mailing list