From 42adaa11aa0a66645d4cf94488c88660cea00c41 Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Mon, 21 Sep 2026 21:34:12 -1000 Subject: Harden the page renderers --- source/ui-snapshot.c | 71 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 11 deletions(-) (limited to 'source/ui-snapshot.c') diff --git a/source/ui-snapshot.c b/source/ui-snapshot.c index a9bf858..5f2d69d 100644 --- a/source/ui-snapshot.c +++ b/source/ui-snapshot.c @@ -15,6 +15,7 @@ #include "cgit.h" #include "filter.h" #include "html.h" +#include "shared.h" #include "ui-shared.h" #include "ui-snapshot.h" @@ -61,6 +62,31 @@ static int write_zip_archive(const char *hex, const char *prefix) return write_archive_format("--format=zip", hex, prefix); } +static int program_on_path(const char *program) +{ + const char *path = getenv("PATH"); + struct strbuf full = STRBUF_INIT; + int found = 0; + + if (!path) + return 0; + while (!found) { + const char *end = strchrnul(path, ':'); + + strbuf_reset(&full); + strbuf_add(&full, path, end - path); + if (!full.len) + strbuf_addch(&full, '.'); + strbuf_addf(&full, "/%s", program); + found = !access(full.buf, X_OK); + if (!*end) + break; + path = end + 1; + } + strbuf_release(&full); + return found; +} + static int write_compressed_tar_archive(const char *hex, const char *prefix, char *argv[]) { struct cgit_exec_filter filter; @@ -76,24 +102,28 @@ static int write_compressed_tar_archive(const char *hex, const char *prefix, cha static int write_tar_gzip_archive(const char *hex, const char *prefix) { char *argv[] = { "gzip", "-n", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } static int write_tar_bzip2_archive(const char *hex, const char *prefix) { char *argv[] = { "bzip2", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } static int write_tar_lzip_archive(const char *hex, const char *prefix) { char *argv[] = { "lzip", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } static int write_tar_xz_archive(const char *hex, const char *prefix) { char *argv[] = { "xz", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } @@ -102,20 +132,21 @@ static int write_tar_zstd_archive(const char *hex, const char *prefix) // Single-threaded like the other compressors, since -T0 would let one // request pin every core. char *argv[] = { "zstd", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } // ui-shared.c reads entry zero as the tar whose signature stands in for every // tar variant, so this order cannot change. const struct cgit_snapshot_format cgit_snapshot_formats[] = { - { ".tar", "application/x-tar", write_tar_archive }, - { ".tar.gz", "application/gzip", write_tar_gzip_archive }, - { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive }, - { ".tar.lz", "application/x-lzip", write_tar_lzip_archive }, - { ".tar.xz", "application/x-xz", write_tar_xz_archive }, - { ".tar.zst", "application/zstd", write_tar_zstd_archive }, - { ".zip", "application/zip", write_zip_archive }, - { NULL } + { ".tar", "application/x-tar", write_tar_archive, NULL }, + { ".tar.gz", "application/gzip", write_tar_gzip_archive, "gzip" }, + { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, "bzip2" }, + { ".tar.lz", "application/x-lzip", write_tar_lzip_archive, "lzip" }, + { ".tar.xz", "application/x-xz", write_tar_xz_archive, "xz" }, + { ".tar.zst", "application/zstd", write_tar_zstd_archive, "zstd" }, + { ".zip", "application/zip", write_zip_archive, NULL }, + { 0 } }; // Each tree is read the first time a signature for that format is asked for, @@ -142,7 +173,10 @@ static int resolves(const char *rev) { struct object_id oid; - return repo_get_oid(the_repository, rev, &oid) == 0; + // The name goes no further than this lookup, so a leading dash is + // fine here, while the wider syntax is refused as in cgit_valid_rev. + return !check_refname_format(rev, REFNAME_ALLOW_ONELEVEL) && + !repo_get_oid(the_repository, rev, &oid); } static const char *ref_from_filename(const struct cgit_repo *repo, const char *filename, @@ -197,14 +231,23 @@ static int send_snapshot(const struct cgit_snapshot_format *format, const char * return 1; } if (!lookup_commit_reference(the_repository, &oid)) { - cgit_print_error_page(400, "Bad Request", "Not a commit reference: %s", hex); + cgit_print_error_page(404, "Not Found", "Not a commit: %s", hex); + return 1; + } + // A compressor that cannot run only shows up as a broken pipe once + // the tar is under way, so it is looked for while a status can still + // say so. + if (format->program && !program_on_path(format->program)) { + cgit_print_error_page(500, "Internal Server Error", "Unable to run %s", format->program); return 1; } ctx.page.mimetype = xstrdup(format->mimetype); ctx.page.filename = xstrdup(filename); cgit_print_http_headers(); init_archivers(); - format->write_func(hex, prefix); + // The archiver reads its arguments as a command line, so it gets the + // resolved id and never the name. + format->write_func(oid_to_hex(&oid), prefix); return 0; } @@ -321,6 +364,12 @@ void cgit_print_snapshot(const char *head, const char *hex, const char *filename return; } + // The name becomes the prefix of every member of the archive, and a + // revision expression such as :/pattern could carry a path in it. + if (!hex && dwim && strchr(filename, '/')) { + cgit_print_error_page(404, "Not Found", "Not found"); + return; + } if (!hex && dwim) { hex = ref_from_filename(ctx.repo, filename, f); if (!hex) { -- cgit v2.8.0