From 43cf875dfe63f84040098bd499c8bbdedf2e2c1e Mon Sep 17 00:00:00 2001 From: Eremey Valetov Date: Sat, 13 Jun 2026 08:35:59 -0400 Subject: [PATCH] cli: reject path-traversal in archive entry names on extraction extract_cb appended a decoded entry name to the destination path with no validation, so a crafted archive whose entry name contained "..", a path separator, or an absolute form could write files outside the chosen destination directory (a Zip-Slip). Each UC2 entry name is a single path component -- the directory tree is rebuilt from dirid parents -- so reject any name that is empty, ".", "..", or contains '/' or '\'. The bundled writer only ever stores basenames, so this affects malformed or hostile archives only; normal extraction (including names like "..foo" and nested directories) is unchanged. --- cli/src/main.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cli/src/main.c b/cli/src/main.c index 5a21606..23df881 100644 --- a/cli/src/main.c +++ b/cli/src/main.c @@ -483,6 +483,19 @@ static bool extract_cb(struct node *ne, void *ctx, enum cause cause) switch (cause) { case VisitFile: case EnterDir:; + /* Each UC2 entry name is a single path component (the directory + tree is rebuilt from dirid parents). A name that is empty, + ".", "..", or contains a path separator is malformed or a + path-traversal attempt -- refuse to extract it rather than + write outside the destination. */ + if (l == 0 + || (l == 1 && e->name[0] == '.') + || (l == 2 && e->name[0] == '.' && e->name[1] == '.') + || memchr(e->name, '/', l) + || memchr(e->name, '\\', l)) + errx(EXIT_FAILURE, "unsafe archive entry name: %.*s", + (int)l, e->name); + char *p = path->ptr + l; if (p + 1 >= endof(path->buffer)) errx(EXIT_FAILURE, "Path too long");