kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit f2c14c6b380edb531729aa4cd195b4a6ec02c27f
parent 1dae315948770c1020a5231df4d4a7698b48aa7c
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 14:36:04 -0700

link: bound link-script expression recursion in parse and eval

Deeply nested expressions (e.g. (((...))) ) recursed unbounded through
parse_atom/parse_expr/parse_binop_rhs and eval_link_expr, risking a C
stack overflow on adversarial scripts. Add a depth counter to the LSP
parser state (guarded in a parse_atom wrapper around parse_atom_inner)
and a depth parameter to eval (eval_link_expr_rec, with a thin
eval_link_expr wrapper entering at depth 0). Both fail cleanly past 256:
the parser emits a diagnostic and unwinds; eval sets *err.

Adds check_recursion_depth_guard (512 nested parens -> clean rejection,
no crash; red->green).

Diffstat:
Msrc/link/link_layout.c | 80+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/link/link_script.c | 27++++++++++++++++++++++++++-
2 files changed, 74 insertions(+), 33 deletions(-)

diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -688,13 +688,22 @@ static void validate_script_target(Linker* l, const KitLinkScript* script) { } } -static u64 eval_link_expr(Linker* l, LinkImage* img, u64 dot, - const ScriptOutInfo* outs, u32 nouts, - const KitLinkExpr* e, int* err) { +/* Maximum link-script expression evaluation depth — mirrors the parser's + * LSP_MAX_EXPR_DEPTH so eval fails cleanly (sets *err) instead of overflowing + * the stack on a pathologically nested expression. */ +#define EVAL_LINK_MAX_DEPTH 256 + +static u64 eval_link_expr_rec(Linker* l, LinkImage* img, u64 dot, + const ScriptOutInfo* outs, u32 nouts, + int depth, const KitLinkExpr* e, int* err) { if (!e) { *err = 1; return 0; } + if (depth > EVAL_LINK_MAX_DEPTH) { + *err = 1; + return 0; + } switch ((KitLinkExprKind)e->kind) { case KIT_LE_INT: return (u64)e->v.int_val; @@ -711,61 +720,61 @@ static u64 eval_link_expr(Linker* l, LinkImage* img, u64 dot, return LinkSyms_at(&img->syms, id - 1)->vaddr; } case KIT_LE_NEG: - return (u64)(-(i64)eval_link_expr(l, img, dot, outs, nouts, - e->v.align.val, err)); + return (u64)(-(i64)eval_link_expr_rec(l, img, dot, outs, nouts, + depth + 1, e->v.align.val, err)); case KIT_LE_ADD: - return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) + - eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err) + + eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); case KIT_LE_SUB: - return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) - - eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err) - + eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); case KIT_LE_MUL: - return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) * - eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err) * + eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); case KIT_LE_DIV: { - u64 rhs = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + u64 rhs = eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); if (rhs == 0) { *err = 1; return 0; } - return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) / rhs; + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err) / rhs; } case KIT_LE_AND: - return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) & - eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err) & + eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); case KIT_LE_OR: - return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) | - eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err) | + eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); case KIT_LE_XOR: - return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) ^ - eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err) ^ + eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); case KIT_LE_SHL: - return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) - << eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err) + << eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); case KIT_LE_SHR: - return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) >> - eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err) >> + eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); case KIT_LE_MAX: { - u64 a = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err); - u64 b = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + u64 a = eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err); + u64 b = eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); return a > b ? a : b; } case KIT_LE_MIN: { - u64 a = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err); - u64 b = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + u64 a = eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.lhs, err); + u64 b = eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.bin.rhs, err); return a < b ? a : b; } case KIT_LE_ALIGN: { - u64 v = eval_link_expr(l, img, dot, outs, nouts, e->v.align.val, err); + u64 v = eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.align.val, err); u64 a = - eval_link_expr(l, img, dot, outs, nouts, e->v.align.align, err); + eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.align.align, err); if (a == 0) return v; return ALIGN_UP(v, a); } case KIT_LE_BLOCK: { - u64 v = eval_link_expr(l, img, dot, outs, nouts, e->v.align.val, err); + u64 v = eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.align.val, err); u64 a = - eval_link_expr(l, img, dot, outs, nouts, e->v.align.align, err); + eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.align.align, err); if (a == 0) return v; return ALIGN_UP(v, a); } @@ -822,7 +831,7 @@ static u64 eval_link_expr(Linker* l, LinkImage* img, u64 dot, return (s && s->defined) ? 1u : 0u; } case KIT_LE_ABSOLUTE: - return eval_link_expr(l, img, dot, outs, nouts, e->v.align.val, err); + return eval_link_expr_rec(l, img, dot, outs, nouts, depth + 1, e->v.align.val, err); default: compiler_panic(l->c, SRCLOC_NONE, "linker script: expression kind %u not supported", @@ -831,6 +840,13 @@ static u64 eval_link_expr(Linker* l, LinkImage* img, u64 dot, } } +/* Depth-tracked entry point: callers evaluate from depth 0. */ +static u64 eval_link_expr(Linker* l, LinkImage* img, u64 dot, + const ScriptOutInfo* outs, u32 nouts, + const KitLinkExpr* e, int* err) { + return eval_link_expr_rec(l, img, dot, outs, nouts, 0, e, err); +} + /* Set a segment's phdr attributes (type/filehdr/phdrs/flags) from the named * PHDRS entry `name`. Panics if the name is unknown. `dot`/`outs`/`nouts` are * forwarded to the FLAGS() expression evaluation. */ diff --git a/src/link/link_script.c b/src/link/link_script.c @@ -73,8 +73,15 @@ typedef struct LSP { /* one-bit error sticky: any diagnostic flips this and the parser * unwinds without producing partial output. */ int err; + /* expression-parse recursion depth (parens / unary / binop RHS); guards the + * descent from overflowing the C stack on pathological input. */ + int expr_depth; } LSP; +/* Maximum expression nesting depth for both parse and eval. Past this we emit + * a clean diagnostic instead of recursing into a stack overflow. */ +#define LSP_MAX_EXPR_DEPTH 256 + /* ---- diagnostics ---- */ static SrcLoc lsp_loc(const LSP* p, size_t off) { @@ -336,6 +343,7 @@ static int match_kw(LSP* p, const char* kw) { */ static KitLinkExpr* parse_expr(LSP* p); +static KitLinkExpr* parse_atom(LSP* p); static KitLinkExpr* parse_int(LSP* p) { KitLinkExpr* e; @@ -396,7 +404,7 @@ static KitLinkExpr* parse_int(LSP* p) { return e; } -static KitLinkExpr* parse_atom(LSP* p) { +static KitLinkExpr* parse_atom_inner(LSP* p) { int ch; skip_ws(p); if (p->err) return NULL; @@ -570,6 +578,23 @@ static KitLinkExpr* parse_atom(LSP* p) { return NULL; } +/* Depth-guarded entry to atom parsing. Every nesting level (parens, unary, + * binop RHS, helper-call argument) descends through parse_atom, so bumping the + * counter here bounds total expression-parse recursion. */ +static KitLinkExpr* parse_atom(LSP* p) { + KitLinkExpr* e; + if (p->err) return NULL; + if (++p->expr_depth > LSP_MAX_EXPR_DEPTH) { + lsp_errf(p, p->pos, "linker-script expression nested too deeply (>%d)", + LSP_MAX_EXPR_DEPTH); + --p->expr_depth; + return NULL; + } + e = parse_atom_inner(p); + --p->expr_depth; + return e; +} + /* Returns >=0 binding power for a binary operator at p->pos and * advances past it; -1 if no binary operator at the lookahead. */ static int try_take_binop(LSP* p, KitLinkExprKind* out_kind) {