archival: disallow path traversals (CVE-2023-39810)
No binary (build failed)
—
(log yok)
| @@ -35,4 +35,15 @@ config FEATURE_LZMA_FAST | |
| This option reduces decompression time by about 25% at the cost of | |
| a 1K bigger binary. | |
| + | config FEATURE_PATH_TRAVERSAL_PROTECTION |
| + | bool "Prevent extraction of filenames with /../ path component" |
| + | default n |
| + | help |
| + | busybox tar and unzip remove "PREFIX/../" (if it exists) |
| + | from extracted names. |
| + | This option enables this behavior for all other unpacking applets, |
| + | such as cpio, ar, rpm. |
| + | GNU cpio 2.15 has NO such sanity check. |
| + | # try other archivers and document their behavior? |
| + | |
| endmenu | |
| @@ -65,6 +65,14 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| } while (--n != 0); | |
| } | |
| #endif | |
| + | #if ENABLE_FEATURE_PATH_TRAVERSAL_PROTECTION |
| + | /* Strip leading "/" and up to last "/../" path component */ |
| + | dst_name = (char *)strip_unsafe_prefix(dst_name); |
| + | #endif |
| + | // ^^^ This may be a problem if some applets do need to extract absolute names. |
| + | // (Probably will need to invent ARCHIVE_ALLOW_UNSAFE_NAME flag). |
| + | // You might think that rpm needs it, but in my tests rpm's internal cpio |
| + | // archive has names like "./usr/bin/FOO", not "/usr/bin/FOO". |
| if (archive_handle->ah_flags & ARCHIVE_CREATE_LEADING_DIRS) { | |
| char *slash = strrchr(dst_name, '/'); | |
| @@ -14,7 +14,11 @@ const char* FAST_FUNC strip_unsafe_prefix(const char *str) | |
| cp++; | |
| continue; | |
| } | |
| - | if (is_prefixed_with(cp, "/../"+1)) { |
| + | /* We are called lots of times. |
| + | * is_prefixed_with(cp, "../") is slower than open-coding it, |
| + | * with minimal code growth (~few bytes). |
| + | */ |
| + | if (cp[0] == '.' && cp[1] == '.' && cp[2] == '/') { |
| cp += 3; | |
| continue; | |
| } | |
| @@ -47,7 +47,7 @@ trap "rm -f $tmp" 0 1 2 3 15 | |
| check() { | |
| $cc -x c - -o $tmp 2>/dev/null <<'EOF' | |
| #include CURSES_LOC | |
| - | main() {} |
| + | int main() { return 0; } |
| EOF | |
| if [ $? != 0 ]; then | |
| echo " *** Unable to find the ncurses libraries or the" 1>&2 | |
| @@ -154,6 +154,29 @@ testing "cpio -R with extract" \ | |
| " "" "" | |
| SKIP= | |
| + | # Create an archive containing a file with "../dont_write" filename. |
| + | # See that it will not be allowed to unpack. |
| + | # NB: GNU cpio 2.15 DOES NOT do such checks. |
| + | optional FEATURE_PATH_TRAVERSAL_PROTECTION |
| + | rm -rf cpio.testdir |
| + | mkdir -p cpio.testdir/prepare/inner |
| + | echo "file outside of destination was written" > cpio.testdir/prepare/dont_write |
| + | echo "data" > cpio.testdir/prepare/inner/to_extract |
| + | mkdir -p cpio.testdir/extract |
| + | testing "cpio extract file outside of destination" "\ |
| + | (cd cpio.testdir/prepare/inner && echo -e '../dont_write\nto_extract' | cpio -o -H newc) | (cd cpio.testdir/extract && cpio -vi 2>&1) |
| + | echo \$? |
| + | ls cpio.testdir/dont_write 2>&1" \ |
| + | "\ |
| + | cpio: removing leading '../' from member names |
| + | ../dont_write |
| + | to_extract |
| + | 1 blocks |
| + | 0 |
| + | ls: cpio.testdir/dont_write: No such file or directory |
| + | " "" "" |
| + | SKIP= |
| + | |
| # Clean up | |
| rm -rf cpio.testdir cpio.testdir2 2>/dev/null | |