[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