w6l: size -L/-l/input arrays by argc, not a fixed 64 (F-C)
libdirs, lflags and inputs were fixed 64-slot arrays written with no
bound check; the 65th -L/-l flag (or input) wrote past the allocation
-> heap corruption. Size all three by argc instead, the true upper
bound since each argv slot yields at most one entry, mirroring cstage
cmd/w6l/main.c:63-67 (calloc(argc, ...)). Drop the now-dead maxinputs
"too many inputs" cap -- cstage has none, and argc-sizing makes it
unreachable.
Regenerates the w6l combined.ww. Table-driven 632 test reaches a lib
only via the Nth -L (N in {1,64,65,100,128}, both stages); pre-fix the
nflags=65 row fails (slot one past the 64-array).
This commit is contained in:
5
Makefile
5
Makefile
@@ -233,6 +233,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_w6c $(BIN)/test_w6a $(BIN)/test_w6a_parsenum \
|
$(BIN)/test_w6c $(BIN)/test_w6a $(BIN)/test_w6a_parsenum \
|
||||||
$(BIN)/test_dataw $(BIN)/test_datar \
|
$(BIN)/test_dataw $(BIN)/test_datar \
|
||||||
$(BIN)/test_w6l $(BIN)/test_data_link \
|
$(BIN)/test_w6l $(BIN)/test_data_link \
|
||||||
|
$(BIN)/test_w6l_manyflags \
|
||||||
$(BIN)/test_arch \
|
$(BIN)/test_arch \
|
||||||
$(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \
|
$(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \
|
||||||
$(BIN)/test_at_test $(BIN)/test_selfimport $(BIN)/test_missingpkg \
|
$(BIN)/test_at_test $(BIN)/test_selfimport $(BIN)/test_missingpkg \
|
||||||
@@ -638,6 +639,10 @@ $(BIN)/test_datar: test/wcc/520_datar.c $(BIN)/w6a $(BIN)/w6l | $(BIN)
|
|||||||
$(BIN)/test_w6l: test/wcc/600_w6l.c $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l | $(BIN)
|
$(BIN)/test_w6l: test/wcc/600_w6l.c $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_w6l_manyflags: test/wcc/632_w6l_manyflags.c $(BIN)/w6c $(BIN)/w6a \
|
||||||
|
$(BIN)/w6l $(BIN)/w6l_ww | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
$(BIN)/test_data_link: test/wcc/620_data_link.c $(BIN)/w6a $(BIN)/w6l | $(BIN)
|
$(BIN)/test_data_link: test/wcc/620_data_link.c $(BIN)/w6a $(BIN)/w6l | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
|||||||
@@ -5226,15 +5226,16 @@ fn isso(path: *u8) i32 = {
|
|||||||
|
|
||||||
export fn main(argc: i32, argv: **u8) i32 = {
|
export fn main(argc: i32, argv: **u8) i32 = {
|
||||||
let outpath: *u8 = nil;
|
let outpath: *u8 = nil;
|
||||||
let maxinputs: i32 = 64;
|
// argc is the upper bound on flags/inputs (one each per argv slot),
|
||||||
let inputs: []*u8 = alloc([], maxinputs: u64)!;
|
// mirroring cstage's calloc(argc, ...). No fixed cap.
|
||||||
inputs.len = maxinputs;
|
let inputs: []*u8 = alloc([], argc: u64)!;
|
||||||
|
inputs.len = argc;
|
||||||
let ninputs: i32 = 0;
|
let ninputs: i32 = 0;
|
||||||
let libdirs: []*u8 = alloc([], maxinputs: u64)!;
|
let libdirs: []*u8 = alloc([], argc: u64)!;
|
||||||
libdirs.len = maxinputs;
|
libdirs.len = argc;
|
||||||
let nlibdirs: i32 = 0;
|
let nlibdirs: i32 = 0;
|
||||||
let lflags: []*u8 = alloc([], maxinputs: u64)!;
|
let lflags: []*u8 = alloc([], argc: u64)!;
|
||||||
lflags.len = maxinputs;
|
lflags.len = argc;
|
||||||
let nlflags: i32 = 0;
|
let nlflags: i32 = 0;
|
||||||
|
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
@@ -5292,11 +5293,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
return 2;
|
return 2;
|
||||||
};};
|
};};
|
||||||
} else {
|
} else {
|
||||||
if (ninputs >= maxinputs) {
|
|
||||||
let m: str = "w6l: too many inputs\n";
|
|
||||||
os.write(2, m.ptr, m.len: u64);
|
|
||||||
return 2;
|
|
||||||
};
|
|
||||||
inputs[ninputs] = a;
|
inputs[ninputs] = a;
|
||||||
ninputs += 1;
|
ninputs += 1;
|
||||||
};};};};
|
};};};};
|
||||||
|
|||||||
@@ -176,15 +176,16 @@ fn isso(path: *u8) i32 = {
|
|||||||
|
|
||||||
export fn main(argc: i32, argv: **u8) i32 = {
|
export fn main(argc: i32, argv: **u8) i32 = {
|
||||||
let outpath: *u8 = nil;
|
let outpath: *u8 = nil;
|
||||||
let maxinputs: i32 = 64;
|
// argc is the upper bound on flags/inputs (one each per argv slot),
|
||||||
let inputs: []*u8 = alloc([], maxinputs: u64)!;
|
// mirroring cstage's calloc(argc, ...). No fixed cap.
|
||||||
inputs.len = maxinputs;
|
let inputs: []*u8 = alloc([], argc: u64)!;
|
||||||
|
inputs.len = argc;
|
||||||
let ninputs: i32 = 0;
|
let ninputs: i32 = 0;
|
||||||
let libdirs: []*u8 = alloc([], maxinputs: u64)!;
|
let libdirs: []*u8 = alloc([], argc: u64)!;
|
||||||
libdirs.len = maxinputs;
|
libdirs.len = argc;
|
||||||
let nlibdirs: i32 = 0;
|
let nlibdirs: i32 = 0;
|
||||||
let lflags: []*u8 = alloc([], maxinputs: u64)!;
|
let lflags: []*u8 = alloc([], argc: u64)!;
|
||||||
lflags.len = maxinputs;
|
lflags.len = argc;
|
||||||
let nlflags: i32 = 0;
|
let nlflags: i32 = 0;
|
||||||
|
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
@@ -242,11 +243,6 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
return 2;
|
return 2;
|
||||||
};};
|
};};
|
||||||
} else {
|
} else {
|
||||||
if (ninputs >= maxinputs) {
|
|
||||||
let m: str = "w6l: too many inputs\n";
|
|
||||||
os.write(2, m.ptr, m.len: u64);
|
|
||||||
return 2;
|
|
||||||
};
|
|
||||||
inputs[ninputs] = a;
|
inputs[ninputs] = a;
|
||||||
ninputs += 1;
|
ninputs += 1;
|
||||||
};};};};
|
};};};};
|
||||||
|
|||||||
151
test/wcc/632_w6l_manyflags.c
Normal file
151
test/wcc/632_w6l_manyflags.c
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* 632_w6l_manyflags — w6l -L/-l arrays must be sized by argc, not a
|
||||||
|
* fixed 64-slot cap (drain F-C). The 65th -L used to write past the
|
||||||
|
* allocation: `libdirs[nlibdirs] = ...` with no bound check, corrupting
|
||||||
|
* the heap and dropping the flag.
|
||||||
|
*
|
||||||
|
* Behavioural discriminator: make a -L past slot 64 LOAD-BEARING. We
|
||||||
|
* build libfoo.a in a real directory and reach it ONLY via the Nth -L,
|
||||||
|
* preceded by N-1 junk dirs. w6l errors `cannot find -lfoo` unless it
|
||||||
|
* can locate the archive, so a successful link (exit 0 + ET_EXEC) proves
|
||||||
|
* the Nth -L was honoured. N is a table {1, 64, 65, 100, 128}; pre-fix,
|
||||||
|
* N=65 (the slot exactly one past the 64-element array) reliably fails
|
||||||
|
* to round-trip and the link errors — verified by rebuilding the pre-fix
|
||||||
|
* w6l_ww. Both stages are driven for parity.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
absbin(void)
|
||||||
|
{
|
||||||
|
const char *b = getenv("BIN");
|
||||||
|
if (!b) b = "out/bin";
|
||||||
|
if (b[0] == '/') return b;
|
||||||
|
static char buf[2048];
|
||||||
|
char cwd[1024];
|
||||||
|
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
||||||
|
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
runwait(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* link `obj` with linker `lnk`, resolving -lfoo through `libdir` placed
|
||||||
|
* as the Nth (last) -L after nflags-1 junk dirs. Returns the linker's
|
||||||
|
* exit code; on success also fails (-1) if the output isn't an ET_EXEC
|
||||||
|
* ELF — proving the archive was actually located and linked. */
|
||||||
|
static int
|
||||||
|
linkwith(const char *lnk, const char *obj, const char *libdir, int nflags)
|
||||||
|
{
|
||||||
|
char exe[64];
|
||||||
|
snprintf(exe, sizeof exe, "/tmp/wwfc_%d_x", getpid());
|
||||||
|
|
||||||
|
char junk[8192];
|
||||||
|
size_t off = 0;
|
||||||
|
for (int i = 1; i < nflags; i++)
|
||||||
|
off += snprintf(junk + off, sizeof junk - off,
|
||||||
|
" -L/nonexist/wwfc_d%d", i);
|
||||||
|
|
||||||
|
char cmd[16384];
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"%s -o %s %s%s -L%s -lfoo 2>/dev/null",
|
||||||
|
lnk, exe, obj, junk, libdir);
|
||||||
|
int rc = runwait(cmd);
|
||||||
|
if (rc != 0) { unlink(exe); return rc; }
|
||||||
|
|
||||||
|
FILE *f = fopen(exe, "rb");
|
||||||
|
if (!f) return -1;
|
||||||
|
unsigned char hdr[20];
|
||||||
|
int ok = fread(hdr, 1, sizeof hdr, f) == sizeof hdr;
|
||||||
|
fclose(f);
|
||||||
|
unlink(exe);
|
||||||
|
if (!ok || memcmp(hdr, "\x7f""ELF", 4) != 0) return -1;
|
||||||
|
unsigned short etype = (unsigned short)hdr[16]
|
||||||
|
| ((unsigned short)hdr[17] << 8);
|
||||||
|
if (etype != 2) return -1; /* ET_EXEC */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
const char *bin = absbin();
|
||||||
|
if (!bin) return 1;
|
||||||
|
|
||||||
|
char src[64], asmf[64], obj[64];
|
||||||
|
char fsrc[64], fasm[64], fobj[64];
|
||||||
|
char libdir[64], lib[128], cmd[1024];
|
||||||
|
int pid = getpid();
|
||||||
|
snprintf(src, sizeof src, "/tmp/wwfc_%d_m.ww", pid);
|
||||||
|
snprintf(asmf, sizeof asmf, "/tmp/wwfc_%d_m.s", pid);
|
||||||
|
snprintf(obj, sizeof obj, "/tmp/wwfc_%d_m.o", pid);
|
||||||
|
snprintf(fsrc, sizeof fsrc, "/tmp/wwfc_%d_f.ww", pid);
|
||||||
|
snprintf(fasm, sizeof fasm, "/tmp/wwfc_%d_f.s", pid);
|
||||||
|
snprintf(fobj, sizeof fobj, "/tmp/wwfc_%d_f.o", pid);
|
||||||
|
snprintf(libdir, sizeof libdir, "/tmp/wwfc_%d_lib", pid);
|
||||||
|
snprintf(lib, sizeof lib, "%s/libfoo.a", libdir);
|
||||||
|
|
||||||
|
/* a standalone main.o (no undefs) + an unrelated archived foo.o. */
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
fputs("fn main() i32 = { return 42; };", f);
|
||||||
|
fclose(f);
|
||||||
|
f = fopen(fsrc, "wb");
|
||||||
|
fputs("export fn foo() i32 = { return 7; };", f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s", bin, asmf, src);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "w6c main failed\n"); return 1; }
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, asmf);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "w6a main failed\n"); return 1; }
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s", bin, fasm, fsrc);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "w6c foo failed\n"); return 1; }
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, fobj, fasm);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "w6a foo failed\n"); return 1; }
|
||||||
|
snprintf(cmd, sizeof cmd, "mkdir -p %s && ar rcs %s %s", libdir, lib, fobj);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "ar failed\n"); return 1; }
|
||||||
|
|
||||||
|
int counts[] = { 1, 64, 65, 100, 128 };
|
||||||
|
int fail = 0;
|
||||||
|
for (int i = 0; i < (int)(sizeof counts / sizeof counts[0]); i++) {
|
||||||
|
int n = counts[i];
|
||||||
|
char wlnk[2048], clnk[2048];
|
||||||
|
snprintf(wlnk, sizeof wlnk, "%s/w6l_ww", bin);
|
||||||
|
snprintf(clnk, sizeof clnk, "%s/w6l", bin);
|
||||||
|
int wrc = linkwith(wlnk, obj, libdir, n);
|
||||||
|
int crc = linkwith(clnk, obj, libdir, n);
|
||||||
|
if (wrc != 0) {
|
||||||
|
fprintf(stderr, "FAIL: w6l_ww nflags=%d link rc=%d "
|
||||||
|
"(65th-style -L not honoured → heap overflow)\n",
|
||||||
|
n, wrc);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
if (crc != 0) {
|
||||||
|
fprintf(stderr, "FAIL: w6l (cstage) nflags=%d link rc=%d\n",
|
||||||
|
n, crc);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s %s %s %s %s %s %s",
|
||||||
|
src, asmf, obj, fsrc, fasm, fobj, libdir);
|
||||||
|
(void)runwait(cmd);
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "w6l_manyflags: %d failure(s)\n", fail);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("w6l_manyflags: -L past slot 64 honoured by both stages "
|
||||||
|
"(1/64/65/100/128 flags)\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user