From a95277cef4e7ca68d5c33c49417c6781b50ba529 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Thu, 4 Jun 2026 12:44:43 -0400 Subject: [PATCH] c: fix buffer overflow in load_file on non-seekable inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bash process substitution <(...) passes /proc/self/fd/N — a pipe, not a regular file. load_file used fseek(SEEK_END)+ftell to size a single- read buffer; on a pipe ftell returns -1, which casts to SIZE_MAX as fread's nbyte argument and blows the heap. Glibc fortify caught it as '*** buffer overflow detected ***'. Detect non-seekable input via the fseek return code and fall back to a doubling growable buffer instead. Seekable path unchanged. Repro: ~/git/lumbda/c/lumbda <(echo '(display 1)(newline)') --- c/eval.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/c/eval.c b/c/eval.c index f26c333..6b861fc 100644 --- a/c/eval.c +++ b/c/eval.c @@ -1636,13 +1636,36 @@ void load_file(const char *path, Env *env) { FILE *f = fopen(path, "r"); if (!f) lisp_error("file not found: %s", path); - fseek(f, 0, SEEK_END); - long sz = ftell(f); - fseek(f, 0, SEEK_SET); - - char *src = (char *)ul_malloc(sz + 1); - size_t nread = fread(src, 1, sz, f); - src[nread] = '\0'; + /* Seekable files (regular): size up-front, single read. + * Non-seekable files (pipes, FIFOs, sockets — e.g. bash process + * substitution <(...) yields /proc/self/fd/N pointing at a pipe): + * ftell returns -1, which silently casts to SIZE_MAX inside fread + * and overruns the heap. Stream-read into a growable buffer instead. */ + char *src = NULL; + long sz = -1; + if (fseek(f, 0, SEEK_END) == 0) { + sz = ftell(f); + if (fseek(f, 0, SEEK_SET) != 0) sz = -1; + } + if (sz >= 0) { + src = (char *)ul_malloc((size_t)sz + 1); + size_t nread = fread(src, 1, (size_t)sz, f); + src[nread] = '\0'; + } else { + size_t cap = 4096; + size_t len = 0; + src = (char *)ul_malloc(cap); + for (;;) { + if (len + 1 >= cap) { + cap *= 2; + src = (char *)ul_realloc(src, cap); + } + size_t got = fread(src + len, 1, cap - 1 - len, f); + len += got; + if (got == 0) break; + } + src[len] = '\0'; + } fclose(f); int count;