c: fix buffer overflow in load_file on non-seekable inputs

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)')
This commit is contained in:
russell@unturf.com 2026-06-04 12:44:43 -04:00
parent 03a3534f0e
commit a95277cef4
No known key found for this signature in database

View file

@ -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;