commit a9068bc8a65245385dab6ef24628b662be82a1fa parent 8dea968324928d6a77b54be45cb1037097f109d8 Author: Ryan Sepassi <rsepassi@gmail.com> Date: Thu, 16 Jul 2026 10:11:51 -0700 c: implement reserved typeof and stack protector modes Diffstat:
36 files changed, 839 insertions(+), 7 deletions(-)
diff --git a/include/kit/cg.h b/include/kit/cg.h @@ -642,6 +642,12 @@ KIT_API KitSym kit_cg_c_linkage_name(KitCompiler*, KitSym source_name); KIT_API void kit_cg_func_begin(KitCg*, KitCgSym sym); KIT_API void kit_cg_func_begin_attrs(KitCg*, KitCgSym sym, KitCgFuncAttrs attrs); +/* Enable stack-canary instrumentation for the current function. Call after + * declaring parameters and before the first protected stack object is used. + * Idempotent. A no-op when KitCodeOptions.stack_protector is NONE. CG snapshots + * the target ABI guard, checks it before every subsequent return, and disables + * sibling/tail exits for the function. */ +KIT_API void kit_cg_stack_protector_enable(KitCg*); KIT_API void kit_cg_func_end(KitCg*); /* Reclaim the storage of dead compiler temporaries. A frontend calls this at a diff --git a/include/kit/core.h b/include/kit/core.h @@ -273,6 +273,16 @@ typedef enum KitAutoVarInit { KIT_AUTOVAR_PATTERN = 2 } KitAutoVarInit; +/* Stack-canary policy. The shared CG pipeline owns guard storage/checks; + * language frontends apply their conventional per-function selection and call + * kit_cg_stack_protector_enable for each selected function. */ +typedef enum KitStackProtectorMode { + KIT_STACK_PROTECTOR_NONE = 0, + KIT_STACK_PROTECTOR_BASIC = 1, + KIT_STACK_PROTECTOR_STRONG = 2, + KIT_STACK_PROTECTOR_ALL = 3, +} KitStackProtectorMode; + typedef struct KitCodeOptions { int opt_level; /* 0 direct; 1 optimized; 2 accepted as an alias for 1 */ bool debug_info; /* emit source/debug records when supported */ @@ -304,6 +314,9 @@ typedef struct KitCodeOptions { * explicit initializer. Frontends that lower locals honor it; others ignore * it. 0 (UNINIT) is the C default. */ uint8_t trivial_auto_var_init; + /* KitStackProtectorMode. Frontends select functions; CG emits the common + * native guard/check sequence and prevents tail exits from bypassing it. */ + uint8_t stack_protector; /* Cross-translation-unit LTO. Drivers that have all sources up front use * this to stage semantic frontends into one KitCg session and finalize once. * Separate compilation still emits an ordinary object until serialized IR diff --git a/lang/c/c.c b/lang/c/c.c @@ -184,6 +184,7 @@ static KitStatus c_frontend_compile_cg(KitFrontendState* frontend, kit_frontend_profile_scope_begin(c, C_PROFILE_SCOPE_PARSE_CODEGEN); parse_c(c, pool, pp, decls, cg, (KitSymVis)fe_opts->code.default_visibility, (int)fe_opts->code.trivial_auto_var_init, + (int)fe_opts->code.stack_protector, fe_opts->code.disabled_backend_features); kit_frontend_profile_scope_end(c, C_PROFILE_SCOPE_PARSE_CODEGEN); diff --git a/lang/c/parse/cg.c b/lang/c/parse/cg.c @@ -648,6 +648,10 @@ void c_cg_call_symbol(Parser* p, KitCgSym sym, u32 nargs, const Type* fn_type) { } void c_cg_ret(Parser* p, int has_value) { + if (p->stack_protector_scan) { + if (has_value) c_cg_drop(p); + return; + } if (has_value) { kit_cg_ret(p->cg); } else if (c_cg_emit_enabled(p)) { @@ -660,6 +664,7 @@ void c_cg_ret(Parser* p, int has_value) { void c_cg_alloca(Parser* p) { const Type* vp = type_ptr(p->pool, type_void(p->pool)); + c_stack_protector_enable(p); kit_cg_alloca(p->cg, 16, c_cg_tid(p, vp)); kit_cg_retag_top(p->cg, vp, 0); } diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -200,6 +200,18 @@ void advance(Parser* p) { } p->replay_active = 0; } + if (p->function_replay_active) { + if (p->function_replay_pos < p->function_replay_len) { + p->cur = p->function_replay[p->function_replay_pos++]; + return; + } + p->function_replay_active = 0; + if (p->function_replay_hold) { + memset(&p->cur, 0, sizeof p->cur); + p->cur.kind = TOK_EOF; + return; + } + } if (p->has_next) { p->cur = p->next; p->has_next = 0; @@ -212,6 +224,10 @@ Tok peek1(Parser* p) { if (p->replay_active && p->replay_pos < p->replay_len) { return p->replay[p->replay_pos]; } + if (p->function_replay_active && + p->function_replay_pos < p->function_replay_len) { + return p->function_replay[p->function_replay_pos]; + } if (!p->has_next) { p->next = fetch_tok(p); p->has_next = 1; @@ -272,6 +288,54 @@ void replay_rewind(Parser* p) { p->has_next = 0; } +/* Buffer a whole function body without feeding its already-preprocessed tokens + * back through pp. The dedicated replay stream lets initializer replay nest + * normally during each semantic pass. */ +static void record_function_body(Parser* p) { + Tok* body = NULL; + u32 len = 0; + u32 cap = 0; + int depth = 0; + if (!is_punct(&p->cur, '{')) perr(p, "internal: function body is not '{'"); + for (;;) { + if (len == cap) { + u32 next_cap = cap ? cap * 2u : 128u; + Tok* next = arena_array(p->pool->arena, Tok, next_cap); + if (!next) perr(p, "out of memory recording function body"); + if (body && len) memcpy(next, body, sizeof(Tok) * len); + body = next; + cap = next_cap; + } + body[len++] = p->cur; + if (is_punct(&p->cur, '{')) { + ++depth; + } else if (is_punct(&p->cur, '}')) { + --depth; + if (depth == 0) break; + } else if (p->cur.kind == TOK_EOF) { + perr(p, "unexpected end of file in function body"); + } + advance(p); + } + p->function_replay = body; + p->function_replay_len = len; + p->function_replay_pos = 1; + p->function_replay_active = 1; + p->function_replay_hold = 1; + p->cur = body[0]; + p->has_next = 0; +} + +static void rewind_function_body(Parser* p, int hold_at_end) { + if (!p->function_replay || !p->function_replay_len) + perr(p, "internal: empty function body replay"); + p->cur = p->function_replay[0]; + p->function_replay_pos = 1; + p->function_replay_active = 1; + p->function_replay_hold = hold_at_end ? 1u : 0u; + p->has_next = 0; +} + /* Count top-level items in a recorded brace list. */ u32 count_recorded_top_level_items(const Tok* vec, u32 len) { u32 count; @@ -553,6 +617,58 @@ static const Type* ty_size_t(Parser* p) { return c_abi_size_type(p->abi, p->pool); } +/* C owns conventional function selection; CG owns the guard/check lowering. + * BASIC follows GCC's classic character-buffer threshold, STRONG covers every + * local array (including arrays nested in records) plus address-taken locals, + * and dynamic alloca selects either mode. */ +static int stack_type_selects(Parser* p, const Type* type, int strong) { + u16 i; + if (!type) return 0; + if (type->kind == TY_ARRAY) { + if (strong) return 1; + if ((type->arr.elem->kind == TY_CHAR || + type->arr.elem->kind == TY_SCHAR || + type->arr.elem->kind == TY_UCHAR) && + !type->arr.incomplete && + c_abi_sizeof(p->abi, p->pool, type) >= 8u) + return 1; + return stack_type_selects(p, type->arr.elem, strong); + } + if (type->kind == TY_STRUCT || type->kind == TY_UNION) { + for (i = 0; i < type->rec.nfields; ++i) { + if (stack_type_selects(p, type->rec.fields[i].type, strong)) return 1; + } + } + return 0; +} + +void c_stack_protector_enable(Parser* p) { + if (!p || p->stack_protector_mode == KIT_STACK_PROTECTOR_NONE) + return; + if (p->stack_protector_scan) { + p->stack_protector_scan_selected = 1; + return; + } + if (p->stack_protector_enabled || !p->cur_func_emits) return; + kit_cg_stack_protector_enable(p->cg); + p->stack_protector_enabled = 1; +} + +void c_stack_protector_note_type(Parser* p, const Type* type) { + if (!p || p->stack_protector_mode == KIT_STACK_PROTECTOR_NONE || + p->stack_protector_mode == KIT_STACK_PROTECTOR_ALL) + return; + if (stack_type_selects( + p, type, + p->stack_protector_mode == KIT_STACK_PROTECTOR_STRONG)) + c_stack_protector_enable(p); +} + +void c_stack_protector_note_address(Parser* p) { + if (p && p->stack_protector_mode == KIT_STACK_PROTECTOR_STRONG) + c_stack_protector_enable(p); +} + /* ============================================================ * Local-variable slot allocation * ============================================================ */ @@ -563,6 +679,7 @@ FrameSlot make_local_aligned(Parser* p, Sym name, const Type* type, SrcLoc loc, FrameSlot s; SymEntry* e; u32 nat = c_abi_alignof(p->abi, p->pool, type); + c_stack_protector_note_type(p, type); memset(&fsd, 0, sizeof fsd); fsd.type = type; fsd.name = name; @@ -861,8 +978,20 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { if (is_punct(&p->cur, '=')) { perr(p, "function declarator cannot have initializer"); } - (void)declare_function(p, name, var_ty, specs, loc, NULL, dinfo.asm_label, - §ion_id, &decl_flags, &alias_target); + if (p->stack_protector_scan) { + SymEntry* prior = scope_lookup(p, name); + SymEntry* e = scope_lookup_current(p, name); + if (!e) e = scope_define(p, name, SEK_FUNC, var_ty); + e->v.sym = prior && prior->kind == SEK_FUNC ? prior->v.sym + : OBJ_SYM_NONE; + sym_set_decl(e, prior ? prior->decl_id : DECL_NONE, DS_EXTERN, + prior ? (DeclLinkage)prior->linkage : DL_EXTERNAL, + prior ? prior->decl_flags : DF_NONE, DSTATE_DECLARED); + } else { + (void)declare_function(p, name, var_ty, specs, loc, NULL, + dinfo.asm_label, §ion_id, &decl_flags, + &alias_target); + } (void)section_id; (void)decl_flags; (void)alias_target; @@ -874,7 +1003,7 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { DeclId did; ObjSymId sym; SymEntry* e; - Sym lname = mint_static_local_sym(p, name); + Sym lname; int has_init; u32 align_eff; has_init = accept_punct(p, '='); @@ -886,6 +1015,24 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { var_ty->arr.incomplete) { var_ty = complete_incomplete_array(p, var_ty); } + if (p->stack_protector_scan) { + e = scope_define_checked(p, name, SEK_GLOBAL, var_ty); + e->v.sym = OBJ_SYM_NONE; + sym_set_decl(e, DECL_NONE, DS_STATIC, DL_NONE, DF_STATIC_LOCAL, + DSTATE_DEFINED); + if (has_init) { + if ((var_ty->kind == TY_ARRAY || var_ty->kind == TY_STRUCT || + var_ty->kind == TY_UNION) && + is_punct(&p->cur, '{')) { + init_at(p, FRAME_SLOT_NONE, var_ty, 0, var_ty); + } else { + parse_assign_expr(p); + c_cg_drop(p); + } + } + return; + } + lname = mint_static_local_sym(p, name); memset(&decl_in, 0, sizeof decl_in); decl_in.name = lname; decl_in.asm_name = dinfo.asm_label; @@ -917,6 +1064,16 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { perr(p, "block-scope extern with initializer not supported"); } prior = scope_lookup(p, name); + if (p->stack_protector_scan) { + e = scope_lookup_current(p, name); + if (!e) e = scope_define(p, name, SEK_GLOBAL, var_ty); + e->v.sym = prior && prior->kind == SEK_GLOBAL ? prior->v.sym + : OBJ_SYM_NONE; + sym_set_decl(e, prior ? prior->decl_id : DECL_NONE, DS_EXTERN, + prior ? (DeclLinkage)prior->linkage : DL_EXTERNAL, + prior ? prior->decl_flags : DF_NONE, DSTATE_DECLARED); + return; + } if (prior && prior->kind == SEK_GLOBAL) { SymEntry* cur = scope_lookup_current(p, name); const Type* composite = NULL; @@ -960,6 +1117,7 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { FrameSlot ptr_slot; SymEntry* sym_entry; VLABound* bounds = NULL; + c_stack_protector_enable(p); /* VLA lowers through dynamic alloca. */ if (p->vla_pending) { byte_slot = finish_vla_layout(p, var_ty, loc, &bounds); ++p->vla_mark; @@ -1325,6 +1483,7 @@ static void parse_function_body(Parser* p, ObjSymId fsym, const Type* fn_ty, * region still gets a real CG-label id rather than the suppression sentinel. */ p->cur_func_emits = (u8)c_cg_emit_enabled(p); + p->stack_protector_enabled = 0; c_cg_func_begin(p, &fd); for (u16 i = 0; i < nparams; ++i) { @@ -1349,6 +1508,10 @@ static void parse_function_body(Parser* p, ObjSymId fsym, const Type* fn_ty, } } + if (p->stack_protector_mode == KIT_STACK_PROTECTOR_ALL || + p->stack_protector_preselected) + c_stack_protector_enable(p); + parse_compound_stmt(p); if (fn_ty->fn.ret && fn_ty->fn.ret->kind != TY_VOID && fn_ty->fn.ret->kind != TY_STRUCT && fn_ty->fn.ret->kind != TY_UNION) { @@ -1448,10 +1611,27 @@ static void parse_external_decl(Parser* p) { u8 saved_func_emits = p->cur_func_emits; p->cur_func_name = name; p->cur_func_ret = fn_ty->fn.ret; + if (p->stack_protector_mode == KIT_STACK_PROTECTOR_BASIC || + p->stack_protector_mode == KIT_STACK_PROTECTOR_STRONG) { + record_function_body(p); + p->stack_protector_scan = 1; + p->stack_protector_scan_selected = 0; + c_cg_codegen_suppress_push(p); + parse_function_body(p, fent->v.sym, fn_ty, abi, infos, nparams, loc, + fn_section_id, fn_decl_flags); + c_cg_codegen_suppress_pop(p); + p->stack_protector_scan = 0; + p->stack_protector_preselected = + p->stack_protector_scan_selected ? 1u : 0u; + rewind_function_body(p, 0); + } if (suppress_body_codegen) c_cg_codegen_suppress_push(p); parse_function_body(p, fent->v.sym, fn_ty, abi, infos, nparams, loc, fn_section_id, fn_decl_flags); if (suppress_body_codegen) c_cg_codegen_suppress_pop(p); + p->stack_protector_preselected = 0; + p->function_replay_active = 0; + p->function_replay_hold = 0; p->cur_func_name = saved_func_name; p->cur_func_ret = saved_func_ret; p->cur_func_emits = saved_func_emits; @@ -1656,6 +1836,7 @@ static u8 parser_default_visibility(KitSymVis vis) { void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg, KitSymVis default_visibility, int auto_var_init, + int stack_protector, uint64_t disabled_backend_features) { Parser p; CKw i; @@ -1670,6 +1851,7 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg, p.pool = pool; p.default_visibility = parser_default_visibility(default_visibility); p.auto_var_init = (u8)auto_var_init; + p.stack_protector_mode = (u8)stack_protector; p.general_regs_only = (disabled_backend_features & KIT_CG_BACKEND_SIMD) != 0; @@ -1763,6 +1945,9 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg, p.sym_volatile_alias = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__volatile__")); p.sym_alignof_alias = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__alignof__")); + p.sym_typeof_alias = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__typeof")); + p.sym_typeof_alias2 = + kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__typeof__")); p.sym_asm_alias = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__asm")); p.sym_inline_alias = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__inline")); p.sym_inline_alias2 = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__inline__")); diff --git a/lang/c/parse/parse.h b/lang/c/parse/parse.h @@ -10,6 +10,7 @@ /* C11 frontend. Reads preprocessed tokens, records C declarations, and drives * the public CG API for executable code and object data. */ void parse_c(Compiler*, Pool*, Pp*, DeclTable*, CG*, KitSymVis, - int auto_var_init, uint64_t disabled_backend_features); + int auto_var_init, int stack_protector, + uint64_t disabled_backend_features); #endif diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -486,11 +486,11 @@ static void c_const_guard_note(Parser* p, const char* message) { c_const_guard_note_at(p, pp_materialize_loc(p->pp, p->cur.loc), message); } -static void c_const_guard_not_eval_push(Parser* p) { +void c_const_guard_not_eval_push(Parser* p) { if (p && p->const_guard_depth) ++p->const_guard_not_eval; } -static void c_const_guard_not_eval_pop(Parser* p) { +void c_const_guard_not_eval_pop(Parser* p) { if (!p || !p->const_guard_depth) return; if (!p->const_guard_not_eval) perr(p, "internal parser constant guard not-evaluated underflow"); @@ -2396,6 +2396,7 @@ void parse_unary(Parser* p) { if (c_cg_top_is_bitfield(p)) perr(p, "cannot take address of bit-field"); if (c_cg_top_is_register(p)) perr(p, "cannot take address of register object"); + c_stack_protector_note_address(p); c_cg_addr(p); return; } diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -247,6 +247,11 @@ typedef struct Parser { u8 default_visibility; /* SymVis */ u8 auto_var_init; /* KitAutoVarInit: implicit init for uninit locals */ u8 general_regs_only; /* -mgeneral-regs-only: reject C FP constructs */ + u8 stack_protector_mode; /* KitStackProtectorMode */ + u8 stack_protector_enabled; /* selected current function */ + u8 stack_protector_scan; /* semantic selection-only body pass */ + u8 stack_protector_scan_selected; + u8 stack_protector_preselected; Tok cur; Tok next; @@ -327,6 +332,8 @@ typedef struct Parser { Sym sym_attribute; Sym sym_volatile_alias; Sym sym_alignof_alias; + Sym sym_typeof_alias; /* __typeof */ + Sym sym_typeof_alias2; /* __typeof__ */ Sym sym_asm_alias; Sym sym_inline_alias; Sym sym_inline_alias2; @@ -402,6 +409,15 @@ typedef struct Parser { u32 replay_pos; u8 replay_active; + /* A function body is buffered once for the stack-protector selection pass + * and replayed through the ordinary parser. This is distinct from `replay`, + * which initializer/type parsing may nest while consuming the body. */ + Tok* function_replay; + u32 function_replay_len; + u32 function_replay_pos; + u8 function_replay_active; + u8 function_replay_hold; + StaticReloc* static_relocs; u32 static_relocs_len; u32 static_relocs_cap; @@ -585,6 +601,8 @@ void parse_expr(Parser* p); void parse_assign_expr(Parser* p); void parse_cond_expr(Parser* p); void parse_unary(Parser* p); +void c_const_guard_not_eval_push(Parser* p); +void c_const_guard_not_eval_pop(Parser* p); typedef struct CConstInt { const Type* type; u64 lo; @@ -649,6 +667,9 @@ void parse_local_decl(Parser* p, const DeclSpecs* specs); FrameSlot make_local(Parser* p, Sym name, const Type* type, SrcLoc loc); FrameSlot make_local_aligned(Parser* p, Sym name, const Type* type, SrcLoc loc, u32 align_override); +void c_stack_protector_enable(Parser* p); +void c_stack_protector_note_type(Parser* p, const Type* type); +void c_stack_protector_note_address(Parser* p); Sym mint_static_local_sym(Parser* p, Sym orig); void record_braced_block(Parser* p); void replay_rewind(Parser* p); diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -620,6 +620,35 @@ const Type* resolve_type_specs(Parser* p, const TypeSpecAccum* a, SrcLoc loc) { * parse_decl_specs * ============================================================ */ +static int is_typeof_spelling(const Parser* p, const Tok* t) { + Sym s; + if (!p || !t || t->kind != TOK_IDENT) return 0; + s = tok_ident(t); + return s == p->sym_typeof_alias || s == p->sym_typeof_alias2; +} + +static const Type* parse_typeof_specifier(Parser* p) { + const Type* ty; + advance(p); /* __typeof / __typeof__ */ + expect_punct(p, '(', "'(' after __typeof"); + if (starts_type_name(p, &p->cur)) { + ty = parse_type_name(p); + } else { + /* Preserve the frontend Type without emitting operand side effects. This + * retains qualified, array, and function types instead of applying the + * usual value conversions. */ + c_const_guard_not_eval_push(p); + c_cg_codegen_suppress_push(p); + parse_expr(p); + ty = c_cg_top_type(p); + c_cg_drop(p); + c_cg_codegen_suppress_pop(p); + c_const_guard_not_eval_pop(p); + } + expect_punct(p, ')', "')' after __typeof operand"); + return ty; +} + int parse_decl_specs(Parser* p, DeclSpecs* out) { TypeSpecAccum acc; SrcLoc loc; @@ -655,6 +684,15 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { seen = 1; continue; } + if (is_typeof_spelling(p, &t)) { + if (tagged_ty || acc.saw_explicit_type) { + perr(p, "conflicting type specifiers (__typeof mixed)"); + } + tagged_ty = parse_typeof_specifier(p); + acc.saw_explicit_type = 1; + seen = 1; + continue; + } if (tkw == KW_STRUCT || tkw == KW_UNION) { TypeKind kind = tkw == KW_STRUCT ? TY_STRUCT : TY_UNION; Attr* anon_attrs = NULL; @@ -1293,6 +1331,7 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { int starts_type_name(const Parser* p, const Tok* t) { if (t->kind != TOK_IDENT) return 0; + if (is_typeof_spelling(p, t)) return 1; CKw k = ident_kw_inline(p, tok_ident(t)); switch (k) { case KW_VOID: diff --git a/src/arch/wasm/emit.c b/src/arch/wasm/emit.c @@ -1629,6 +1629,8 @@ static const char* intrin_name(IntrinKind k) { return "return_address"; case INTRIN_READCYCLECOUNTER: return "readcyclecounter"; + case INTRIN_STACK_GUARD: + return "stack_guard"; } return "<unknown>"; } @@ -1778,6 +1780,7 @@ void wasm_intrinsic(CGTarget* tg, IntrinKind k, Operand* dst, u32 ndst, case INTRIN_IRQ_DISABLE: case INTRIN_SYSCALL: case INTRIN_READCYCLECOUNTER: + case INTRIN_STACK_GUARD: /* No frame-pointer chain in wasm; reported unsupported up front. */ case INTRIN_FRAME_ADDRESS: case INTRIN_RETURN_ADDRESS: diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c @@ -3542,6 +3542,20 @@ static void emit_rdtsc(MCEmitter* mc) { mc_emit_bytes(mc, b, 2); } +/* Linux/Android x86-64 stack-protector guard: mov rd, qword ptr fs:[0x28]. */ +static void emit_stack_guard(MCEmitter* mc, u32 rd) { + u8 fs = 0x64; + u8 op = X64_OPC_MOV_R_RM; + u8 mr = modrm(0u, rd & 7u, 4u); + u8 s = sib(0u, 4u, 5u); + mc_emit_bytes(mc, &fs, 1); + emit_rex(mc, 1, rd, 0, 0); + mc_emit_bytes(mc, &op, 1); + mc_emit_bytes(mc, &mr, 1); + mc_emit_bytes(mc, &s, 1); + emit_u32le(mc, 0x28u); +} + static void x64_intrinsic(NativeTarget* t, IntrinKind kind, const NativeLoc* dsts, u32 ndst, const NativeLoc* args, u32 narg) { @@ -3576,6 +3590,11 @@ static void x64_intrinsic(NativeTarget* t, IntrinKind kind, if (rd != X64_RAX) emit_mov_rr(mc, 1, rd, X64_RAX); return; } + case INTRIN_STACK_GUARD: + if (ndst != 1u || narg != 0u) + x64_panic(a, "stack guard intrinsic has invalid operands"); + emit_stack_guard(mc, loc_reg(dsts[0])); + return; case INTRIN_SYSCALL: if (ndst == 1u && narg >= 1u && narg <= 7u) { static const u32 syscall_regs[7] = {X64_RAX, X64_RDI, X64_RSI, X64_RDX, diff --git a/src/cg/call.c b/src/cg/call.c @@ -162,6 +162,14 @@ static int api_tail_decide(KitCg* g, const CGCallDesc* desc, "enclosing function's return type"); return 0; } + if (g->stack_protected) { + if (policy == KIT_CG_TAIL_MUST) { + compiler_panic(g->c, g->cur_loc, + "musttail call not realizable: stack protector requires " + "a checked return epilogue"); + } + return 0; + } reason = T->tail_call_unrealizable_reason ? T->tail_call_unrealizable_reason(T, desc) : "target does not support tail calls"; @@ -336,6 +344,7 @@ void kit_cg_ret(KitCg* g) { } return; } + if (g->stack_protected) api_stack_protector_check(g); if (!api_type_has_value(g, rty)) { g->target->ret(g->target, CG_LOCAL_NONE); return; diff --git a/src/cg/cgir.h b/src/cg/cgir.h @@ -198,6 +198,11 @@ typedef enum IntrinKind { * unsigned 64-bit result. Modeled like a frame-dependent read (IR_INTRINSIC * is conservatively side-effecting, so it is never hoisted/CSE'd/removed). */ INTRIN_READCYCLECOUNTER, + + /* Internal stack-protector guard read. No operands; dsts[0] receives one + * pointer-width word. This is emitted only for target ABIs whose guard has + * no linkable object symbol (Linux/Android x86-64 use fs:0x28). */ + INTRIN_STACK_GUARD, } IntrinKind; typedef enum OpKind { diff --git a/src/cg/internal.h b/src/cg/internal.h @@ -336,7 +336,14 @@ struct KitCg { u8 check_only; u8 function_sections; u8 data_sections; - u8 section_pad[1]; + u8 stack_protector_mode; /* KitStackProtectorMode */ + u8 stack_protected; + u8 stack_guard_tls; + u8 stack_protector_pad[1]; + KitCgTypeId stack_guard_type; + KitCgLocal stack_guard_local; + KitCgSym stack_guard_sym; + KitCgSym stack_fail_sym; ObjSecId data_sec; ObjSymId data_sym; @@ -455,6 +462,7 @@ void api_call_symbol_common(KitCg* g, KitCgSym sym, uint32_t nargs, void kit_cg_call_symbol(KitCg* g, KitCgSym sym, uint32_t nargs, KitCgCallAttrs attrs); void kit_cg_ret(KitCg* g); +void api_stack_protector_check(KitCg* g); KitCgLabel kit_cg_label_new(KitCg* g); void kit_cg_label_place(KitCg* g, KitCgLabel label); void kit_cg_jump(KitCg* g, KitCgLabel label); diff --git a/src/cg/session.c b/src/cg/session.c @@ -127,6 +127,15 @@ KitStatus kit_cg_begin(KitCg* g, KitObjBuilder* out, "KitCg: unsupported opt_level %d", opt_level); } if (opt_level > 1) opt_level = 1; + if (opts && opts->stack_protector > KIT_STACK_PROTECTOR_ALL) { + compiler_panic((Compiler*)c, api_no_loc(), + "KitCg: invalid stack protector mode %u", + (unsigned)opts->stack_protector); + } + /* Portable-C is a semantic replay backend, not a native optimizer target. + * Normalize centrally before either the optimizer wrapper or backend is + * constructed so every frontend/API caller gets the same safe behavior. */ + if (opts && opts->emit_c_source) opt_level = 0; if (opts && opts->emit_ir && opt_level < 1) { compiler_panic((Compiler*)c, api_no_loc(), "KitCg: emit_ir requires opt_level >= 1 " @@ -174,6 +183,13 @@ KitStatus kit_cg_begin(KitCg* g, KitObjBuilder* out, g->check_only = (opts && opts->check_only) ? 1u : 0u; g->function_sections = (opts && opts->function_sections) ? 1u : 0u; g->data_sections = (opts && opts->data_sections) ? 1u : 0u; + /* Stack instrumentation is owned by the native pipeline. Portable-C hands + * hardening policy to the compiler consuming the generated C, and Wasm has + * no native stack-guard ABI for this option. */ + g->stack_protector_mode = + (opts && !opts->emit_c_source && c->target.arch != KIT_ARCH_WASM) + ? opts->stack_protector + : 0u; g->nsource_units = 0; g->unit_active = 0; g->finished = 0; @@ -428,6 +444,12 @@ void kit_cg_func_begin_attrs(KitCg* g, KitCgSym cg_sym, g->nlocals = 0; g->const_head = KIT_CG_LOCAL_NONE; g->sp = 0; + g->stack_protected = 0; + g->stack_guard_tls = 0; + g->stack_guard_type = KIT_CG_TYPE_NONE; + g->stack_guard_local = KIT_CG_LOCAL_NONE; + g->stack_guard_sym = KIT_CG_SYM_NONE; + g->stack_fail_sym = KIT_CG_SYM_NONE; /* New generation: stale local_temp_gen entries from the previous function are * now != func_gen, so its temp handles read as non-temp without any clear. */ g->func_gen++; @@ -507,6 +529,8 @@ void kit_cg_func_end(KitCg* g) { api_debug_emit_source_locals(g); if (g->debug) debug_func_end(g->debug); g->fn_ret_type = KIT_CG_TYPE_NONE; + g->stack_protected = 0; + g->stack_guard_local = KIT_CG_LOCAL_NONE; g->nscopes = 0; /* Clear active/generation on the now-dead scope slots so a stray handle * from this function is caught as stale in the next one. */ diff --git a/src/cg/stack_protector.c b/src/cg/stack_protector.c @@ -0,0 +1,131 @@ +#include "cg/internal.h" + +static KitSym stack_linkage_name(KitCg* g, const char* name) { + Sym source = pool_intern_slice(g->c->global, slice_from_cstr(name)); + return kit_cg_c_linkage_name((KitCompiler*)g->c, source); +} + +static KitCgSym stack_guard_decl(KitCg* g, KitCgTypeId ty, + const char* name) { + KitCgDecl decl; + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_OBJECT; + decl.linkage_name = stack_linkage_name(g, name); + decl.display_name = decl.linkage_name; + decl.type = ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.sym.visibility = KIT_CG_VIS_DEFAULT; + return kit_cg_decl(g, decl); +} + +static KitCgSym stack_func_decl(KitCg* g, const char* name, + KitCgTypeId param, int noreturn) { + KitCgFuncParam p; + KitCgFuncResult result; + KitCgFuncSig sig; + KitCgDecl decl; + memset(&p, 0, sizeof p); + memset(&result, 0, sizeof result); + memset(&sig, 0, sizeof sig); + memset(&decl, 0, sizeof decl); + p.type = param; + result.type = kit_cg_type_builtin((KitCompiler*)g->c, KIT_CG_BUILTIN_VOID); + sig.result = result; + sig.params = param ? &p : NULL; + sig.nparams = param ? 1u : 0u; + sig.call_conv = KIT_CG_CC_TARGET_C; + decl.kind = KIT_CG_DECL_FUNC; + decl.linkage_name = stack_linkage_name(g, name); + decl.display_name = decl.linkage_name; + decl.type = kit_cg_type_func((KitCompiler*)g->c, sig); + decl.sym.bind = KIT_SB_GLOBAL; + decl.sym.visibility = KIT_CG_VIS_DEFAULT; + if (noreturn) decl.as.func.flags |= KIT_CG_FUNC_NORETURN; + return kit_cg_decl(g, decl); +} + +static KitCgMemAccess stack_guard_access(KitCg* g) { + KitCgMemAccess access; + memset(&access, 0, sizeof access); + access.type = g->stack_guard_type; + access.align = g->c->target.ptr_align ? g->c->target.ptr_align + : g->c->target.ptr_size; + return access; +} + +static int stack_guard_uses_x64_tls(KitTargetSpec spec) { + return spec.arch == KIT_ARCH_X86_64 && + (spec.os == KIT_OS_LINUX || spec.os == KIT_OS_ANDROID); +} + +/* Push the process/thread's current guard value. Most supported ABIs expose + * __stack_chk_guard as an ordinary object. Linux and Android x86-64 instead + * reserve the word at fs:0x28, so route that read through the native backend + * without manufacturing an undefined object reference. */ +static void stack_guard_push_current(KitCg* g) { + if (g->stack_guard_tls) { + CGLocal local = api_alloc_temp_local(g, g->stack_guard_type); + Operand dst = api_op_local(local, g->stack_guard_type); + g->target->intrinsic(g->target, INTRIN_STACK_GUARD, &dst, 1, NULL, 0); + api_push(g, api_make_sv(dst, g->stack_guard_type)); + return; + } + kit_cg_push_symbol_addr(g, g->stack_guard_sym, 0); + kit_cg_deref(g, 0); + kit_cg_load(g, stack_guard_access(g)); +} + +void kit_cg_stack_protector_enable(KitCg* g) { + KitTargetSpec spec; + KitCgBuiltinType guard_builtin; + KitCgLocalAttrs attrs; + KitCgMemAccess access; + if (!g || g->stack_protected || + g->stack_protector_mode == KIT_STACK_PROTECTOR_NONE || + g->fn_ret_type == KIT_CG_TYPE_NONE) + return; + + spec = kit_compiler_target_spec((KitCompiler*)g->c); + guard_builtin = spec.ptr_size == 4u ? KIT_CG_BUILTIN_I32 + : KIT_CG_BUILTIN_I64; + g->stack_guard_type = + kit_cg_type_builtin((KitCompiler*)g->c, guard_builtin); + g->stack_guard_tls = stack_guard_uses_x64_tls(spec) ? 1u : 0u; + if (!g->stack_guard_tls) + g->stack_guard_sym = + stack_guard_decl(g, g->stack_guard_type, "__stack_chk_guard"); + g->stack_fail_sym = + stack_func_decl(g, "__stack_chk_fail", KIT_CG_TYPE_NONE, 1); + + memset(&attrs, 0, sizeof attrs); + attrs.name = pool_intern_slice(g->c->global, SLICE_LIT("__kit_stack_guard")); + attrs.align = spec.ptr_align; + attrs.flags = KIT_CG_LOCAL_ARTIFICIAL | KIT_CG_LOCAL_MEMORY_REQUIRED; + g->stack_guard_local = kit_cg_local(g, g->stack_guard_type, attrs); + + access = stack_guard_access(g); + kit_cg_push_local(g, g->stack_guard_local); + stack_guard_push_current(g); + kit_cg_store(g, access); + g->stack_protected = 1; +} + +void api_stack_protector_check(KitCg* g) { + KitCgMemAccess access; + KitCgCallAttrs attrs; + if (!g || !g->stack_protected) return; + access = stack_guard_access(g); + memset(&attrs, 0, sizeof attrs); + + { + KitCgLabel ok = kit_cg_label_new(g); + kit_cg_push_local(g, g->stack_guard_local); + kit_cg_load(g, access); + stack_guard_push_current(g); + kit_cg_int_cmp(g, KIT_CG_INT_NE); + kit_cg_branch_false(g, ok); + kit_cg_call_symbol(g, g->stack_fail_sym, 0, attrs); + kit_cg_unreachable(g); + kit_cg_label_place(g, ok); + } +} diff --git a/src/core/config_assert.c b/src/core/config_assert.c @@ -84,6 +84,7 @@ KIT_ASSERT_BOOL(KIT_TOOL_DISAS_ENABLED); KIT_ASSERT_BOOL(KIT_TOOL_MC_ENABLED); KIT_ASSERT_BOOL(KIT_TOOL_GRAM_ENABLED); KIT_ASSERT_BOOL(KIT_TOOL_UPDATE_ENABLED); +KIT_ASSERT_BOOL(KIT_TOOL_TARGETS_ENABLED); #undef KIT_ASSERT_BOOL diff --git a/test/driver/fixtures/stack_address.c b/test/driver/fixtures/stack_address.c @@ -0,0 +1,5 @@ +int stack_address(void) { + int value = 0; + int* address = &value; + return *address; +} diff --git a/test/driver/fixtures/stack_char4.c b/test/driver/fixtures/stack_char4.c @@ -0,0 +1,5 @@ +int stack_char4(void) { + volatile char buffer[4]; + buffer[0] = 1; + return buffer[0] - 1; +} diff --git a/test/driver/fixtures/stack_char8.c b/test/driver/fixtures/stack_char8.c @@ -0,0 +1,6 @@ +int stack_char8(int path) { + volatile char buffer[8]; + buffer[0] = 1; + if (path) return buffer[0] - 1; + return buffer[0] - 1; +} diff --git a/test/driver/fixtures/stack_fail_only.c b/test/driver/fixtures/stack_fail_only.c @@ -0,0 +1,4 @@ +_Noreturn void __stack_chk_fail(void) { + for (;;) { + } +} diff --git a/test/driver/fixtures/stack_int_array.c b/test/driver/fixtures/stack_int_array.c @@ -0,0 +1,5 @@ +int stack_int_array(void) { + volatile int values[1]; + values[0] = 0; + return values[0]; +} diff --git a/test/driver/fixtures/stack_main.c b/test/driver/fixtures/stack_main.c @@ -0,0 +1,5 @@ +int main(void) { + volatile char buffer[8]; + buffer[0] = 1; + return buffer[0] - 1; +} diff --git a/test/driver/fixtures/stack_overflow.c b/test/driver/fixtures/stack_overflow.c @@ -0,0 +1,10 @@ +void stack_overflow_write(volatile unsigned char*); + +static int protected_overflow(void) { + volatile unsigned char buffer[8]; + buffer[0] = 0; + stack_overflow_write(buffer); + return buffer[0]; +} + +int main(void) { return protected_overflow(); } diff --git a/test/driver/fixtures/stack_overflow_write.c b/test/driver/fixtures/stack_overflow_write.c @@ -0,0 +1 @@ +void stack_overflow_write(volatile unsigned char* p) { p[8] ^= 1u; } diff --git a/test/driver/fixtures/stack_plain.c b/test/driver/fixtures/stack_plain.c @@ -0,0 +1 @@ +int stack_plain(int value) { return value + 1; } diff --git a/test/driver/fixtures/stack_runtime.c b/test/driver/fixtures/stack_runtime.c @@ -0,0 +1,19 @@ +void stack_smash_guard(void); + +static int protected_return(int path) { + if (path == 2) { + stack_smash_guard(); + return 0; + } + volatile char buffer[8]; + buffer[0] = 1; + stack_smash_guard(); + if (path) return buffer[0] - 1; + return buffer[0] - 1; +} + +#ifndef STACK_PATH +#define STACK_PATH 0 +#endif + +int main(void) { return protected_return(STACK_PATH); } diff --git a/test/driver/fixtures/stack_support_bad.c b/test/driver/fixtures/stack_support_bad.c @@ -0,0 +1,8 @@ +unsigned long __stack_chk_guard = 0x79b52d3c18a647e1ul; + +void __stack_chk_fail(void) { + extern void exit(int); + exit(99); +} + +void stack_smash_guard(void) { __stack_chk_guard ^= 0x100ul; } diff --git a/test/driver/fixtures/stack_support_ok.c b/test/driver/fixtures/stack_support_ok.c @@ -0,0 +1,8 @@ +unsigned long __stack_chk_guard = 0x79b52d3c18a647e1ul; + +void __stack_chk_fail(void) { + extern void exit(int); + exit(99); +} + +void stack_smash_guard(void) {} diff --git a/test/driver/stack_protector.sh b/test/driver/stack_protector.sh @@ -0,0 +1,241 @@ +#!/bin/sh +# Focused driver/CG coverage for the native stack protector. Selection tests +# inspect undefined ABI symbols in relocatable objects; runtime tests mutate +# the process guard after the function-entry snapshot and exercise both return +# arms at every supported optimization spelling. + +set -u + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../.." && pwd) +fixtures="$script_dir/fixtures" +KIT="${KIT:-$repo_root/build/kit}" + +if [ ! -x "$KIT" ]; then + echo "stack-protector: kit binary not found at $KIT" >&2 + exit 2 +fi + +work=$(mktemp -d "${TMPDIR:-/tmp}/kit-stack-protector.XXXXXX") +trap 'rm -rf "$work"' EXIT + +KIT_KIT_DIR="$repo_root/test/lib" +. "$repo_root/test/lib/kit_sh_kit.sh" +kit_report_init + +has_symbol() { + name=$1 + file=$2 + needle=$3 + if "$KIT" nm "$file" > "$work/$name.nm" 2> "$work/$name.nm.err" && + grep -F "$needle" "$work/$name.nm" >/dev/null 2>&1; then + ok "$name" + else + { + printf 'missing symbol substring: %s\n' "$needle" + sed 's/^/nm: /' "$work/$name.nm" 2>/dev/null + sed 's/^/err: /' "$work/$name.nm.err" 2>/dev/null + } > "$work/$name.diag" + not_ok "$name" "$work/$name.diag" + fi +} + +lacks_symbol() { + name=$1 + file=$2 + needle=$3 + if "$KIT" nm "$file" > "$work/$name.nm" 2> "$work/$name.nm.err" && + ! grep -F "$needle" "$work/$name.nm" >/dev/null 2>&1; then + ok "$name" + else + { + printf 'unexpected symbol substring: %s\n' "$needle" + sed 's/^/nm: /' "$work/$name.nm" 2>/dev/null + sed 's/^/err: /' "$work/$name.nm.err" 2>/dev/null + } > "$work/$name.diag" + not_ok "$name" "$work/$name.diag" + fi +} + +# Conventional frontend selection. Basic protects character arrays of at +# least eight bytes; strong additionally protects any array or address-taken +# local; all protects even a function without local storage. +run_ok select-basic-plain "$KIT" cc -fstack-protector -c \ + "$fixtures/stack_plain.c" -o "$work/basic-plain.o" +lacks_symbol select-basic-plain-symbol "$work/basic-plain.o" stack_chk + +run_ok select-basic-char4 "$KIT" cc -fstack-protector -c \ + "$fixtures/stack_char4.c" -o "$work/basic-char4.o" +lacks_symbol select-basic-char4-symbol "$work/basic-char4.o" stack_chk + +run_ok select-basic-char8 "$KIT" cc -fstack-protector -c \ + "$fixtures/stack_char8.c" -o "$work/basic-char8.o" +has_symbol select-basic-char8-guard "$work/basic-char8.o" stack_chk_guard +has_symbol select-basic-char8-fail "$work/basic-char8.o" stack_chk_fail + +run_ok select-basic-int-array "$KIT" cc -fstack-protector -c \ + "$fixtures/stack_int_array.c" -o "$work/basic-int.o" +lacks_symbol select-basic-int-array-symbol "$work/basic-int.o" stack_chk + +run_ok select-strong-int-array "$KIT" cc -fstack-protector-strong -c \ + "$fixtures/stack_int_array.c" -o "$work/strong-int.o" +has_symbol select-strong-int-array-guard "$work/strong-int.o" stack_chk_guard +has_symbol select-strong-int-array-fail "$work/strong-int.o" stack_chk_fail + +run_ok select-strong-address "$KIT" cc -fstack-protector-strong -c \ + "$fixtures/stack_address.c" -o "$work/strong-address.o" +has_symbol select-strong-address-guard "$work/strong-address.o" stack_chk_guard +has_symbol select-strong-address-fail "$work/strong-address.o" stack_chk_fail + +run_ok select-all-plain "$KIT" cc -fstack-protector-all -c \ + "$fixtures/stack_plain.c" -o "$work/all-plain.o" +has_symbol select-all-plain-guard "$work/all-plain.o" stack_chk_guard +has_symbol select-all-plain-fail "$work/all-plain.o" stack_chk_fail + +run_ok select-last-option-wins "$KIT" cc -fstack-protector-all \ + -fno-stack-protector -c "$fixtures/stack_char8.c" \ + -o "$work/disabled-char8.o" +lacks_symbol select-last-option-wins-symbol "$work/disabled-char8.o" stack_chk + +run_fail select-unknown-mode "$KIT" cc -fstack-protector-explicit \ + -c "$fixtures/stack_plain.c" -o "$work/unknown.o" +contains select-unknown-mode-diag "$work/select-unknown-mode.err" \ + "unsupported stack protector mode" + +# All three native backends must retain ABI guard/failure references at O0 and +# through the optimizer (-O2 is the supported alias for -O1). +for target in aarch64-none-elf x86_64-none-elf riscv64-none-elf; do + for opt in 0 1 2; do + tag=$(printf '%s-O%s' "$target" "$opt" | tr _ -) + obj="$work/$tag.o" + run_ok "cross-$tag" "$KIT" cc -target "$target" -ffreestanding \ + -O"$opt" -fstack-protector -c "$fixtures/stack_char8.c" -o "$obj" + has_symbol "cross-$tag-guard" "$obj" stack_chk_guard + has_symbol "cross-$tag-fail" "$obj" stack_chk_fail + done +done + +# On glibc AArch64/RISC-V, __stack_chk_guard is exported by the ELF +# interpreter rather than libc. Exercise that linker lane hermetically with a +# tiny DSO that provides only the failure hook: the guard must remain a dynamic +# object import and must not add ld-linux to DT_NEEDED. +for row in 'aa64 aarch64-linux-gnu' 'rv64 riscv64-linux-gnu'; do + set -- $row + short=$1 + target=$2 + fail_obj="$work/loader-$short-fail.o" + fail_dso="$work/loader-$short-libstackfail.so" + protected_obj="$work/loader-$short-protected.o" + image="$work/loader-$short-image" + run_ok "loader-$short-fail-object" "$KIT" cc -target "$target" \ + -ffreestanding -fPIC -c "$fixtures/stack_fail_only.c" -o "$fail_obj" + run_ok "loader-$short-fail-dso" "$KIT" ld -target "$target" -shared \ + -soname libstackfail.so -o "$fail_dso" "$fail_obj" + run_ok "loader-$short-protected-object" "$KIT" cc -target "$target" \ + -ffreestanding -O1 -fstack-protector -c "$fixtures/stack_char8.c" \ + -o "$protected_obj" + run_ok "loader-$short-link" "$KIT" ld -target "$target" -pie \ + -e stack_char8 -o "$image" "$protected_obj" "$fail_dso" + if "$KIT" objdump -p -T -R "$image" > "$work/loader-$short.dump" \ + 2> "$work/loader-$short.dump.err" && + grep -F ' U ' "$work/loader-$short.dump" | + grep -F '__stack_chk_guard' >/dev/null 2>&1 && + grep -F 'NEEDED libstackfail.so' "$work/loader-$short.dump" \ + >/dev/null 2>&1 && + grep -F '__stack_chk_guard' "$work/loader-$short.dump" | + grep -E 'GLOB_DAT|R_RISCV_64' >/dev/null 2>&1 && + ! grep -F 'NEEDED ld-linux' "$work/loader-$short.dump" \ + >/dev/null 2>&1; then + ok "loader-$short-guard-import" + else + { + printf 'expected interpreter-owned dynamic guard import\n' + sed 's/^/dump: /' "$work/loader-$short.dump" 2>/dev/null + sed 's/^/err: /' "$work/loader-$short.dump.err" 2>/dev/null + } > "$work/loader-$short.diag" + not_ok "loader-$short-guard-import" "$work/loader-$short.diag" + fi +done + +# The supported Windows SDK is LLVM-MinGW, whose CRT supplies the GNU +# stack-protector guard and failure hook in libmingwex. +for target in aarch64-windows x86_64-windows; do + tag=$(printf '%s' "$target" | tr _ -) + obj="$work/$tag.o" + run_ok "windows-$tag" "$KIT" cc -target "$target" -ffreestanding \ + -O1 -fstack-protector-all -c "$fixtures/stack_plain.c" -o "$obj" + has_symbol "windows-$tag-guard" "$obj" stack_chk_guard + has_symbol "windows-$tag-fail" "$obj" stack_chk_fail + lacks_symbol "windows-$tag-no-msvc-cookie" "$obj" security_cookie +done + +# Linux/Android x86-64 reserve the guard at fs:0x28 rather than exporting a +# __stack_chk_guard object. The object must carry only the failure reference; +# disassembly verifies that both entry and return checks use the segment read. +for target in x86_64-linux-gnu x86_64-linux-android; do + for opt in 0 1 2; do + tag=$(printf '%s-O%s' "$target" "$opt" | tr _ -) + obj="$work/$tag.o" + run_ok "tls-$tag" "$KIT" cc -target "$target" -ffreestanding \ + -O"$opt" -fstack-protector-all -c "$fixtures/stack_plain.c" -o "$obj" + lacks_symbol "tls-$tag-no-guard-symbol" "$obj" stack_chk_guard + has_symbol "tls-$tag-fail" "$obj" stack_chk_fail + # Kit's current x64 disassembler prints a segment override as a standalone + # `.byte 0x64` immediately before the decoded mov; count those prefixes. + if "$KIT" objdump -d "$obj" > "$work/$tag.dis" 2> "$work/$tag.dis.err" && + [ "$(grep -c '\.byte 0x64' "$work/$tag.dis" 2>/dev/null || true)" -ge 2 ]; then + ok "tls-$tag-fs-guard-loads" + else + { + printf 'expected two fs-segment guard loads\n' + sed 's/^/dis: /' "$work/$tag.dis" 2>/dev/null + sed 's/^/err: /' "$work/$tag.dis.err" 2>/dev/null + } > "$work/$tag.dis.diag" + not_ok "tls-$tag-fs-guard-loads" "$work/$tag.dis.diag" + fi + done +done + +# A hosted executable with an unchanged guard returns normally. Mutating the +# guard after the entry snapshot must reach __stack_chk_fail on both explicit +# return arms at O0, O1, and O2. +for opt in 0 1 2; do + for path in 0 1 2; do + tag=runtime-O$opt-path$path + run_ok "$tag-build-ok" "$KIT" cc -O"$opt" -fstack-protector \ + -DSTACK_PATH="$path" "$fixtures/stack_runtime.c" \ + "$fixtures/stack_support_ok.c" -o "$work/$tag-ok" + run_ok "$tag-run-ok" "$work/$tag-ok" + + run_ok "$tag-build-bad" "$KIT" cc -O"$opt" -fstack-protector \ + -DSTACK_PATH="$path" "$fixtures/stack_runtime.c" \ + "$fixtures/stack_support_bad.c" -o "$work/$tag-bad" + run_fail "$tag-run-bad" "$work/$tag-bad" + done + + # A separate-TU write one byte beyond an eight-byte buffer lands on the + # entry snapshot itself. Require the failure hook's exact status, proving + # this is a real frame canary rather than only a symbol/check scaffold. + overflow=runtime-O$opt-overflow + run_ok "$overflow-build" "$KIT" cc -O"$opt" -fstack-protector \ + "$fixtures/stack_overflow.c" "$fixtures/stack_overflow_write.c" \ + "$fixtures/stack_support_ok.c" -o "$work/$overflow" + if "$work/$overflow" > "$work/$overflow.out" 2> "$work/$overflow.err"; then + overflow_rc=0 + else + overflow_rc=$? + fi + if [ "$overflow_rc" -eq 99 ]; then + ok "$overflow-run" + else + { + printf 'expected stack failure status 99, got %s\n' "$overflow_rc" + sed 's/^/out: /' "$work/$overflow.out" 2>/dev/null + sed 's/^/err: /' "$work/$overflow.err" 2>/dev/null + } > "$work/$overflow.diag" + not_ok "$overflow-run" "$work/$overflow.diag" + fi +done + +kit_summary stack-protector +kit_exit diff --git a/test/parse/cases/gnu_typeof_01_forms.c b/test/parse/cases/gnu_typeof_01_forms.c @@ -0,0 +1,17 @@ +static int calls; + +static int plus_one(int x) { return x + 1; } + +int test_main(void) { + int array[3] = {1, 2, 3}; + const int qualified = 7; + __typeof__(calls++) value = 5; + __typeof(array) array_copy = {4, 5, 6}; + __typeof__(plus_one)* function_pointer = plus_one; + __typeof(&qualified) pointer = &qualified; + __typeof(const int[2]) typed_array = {8, 9}; + __typeof__(qualified) qualified_copy = 10; + + return calls + value + array_copy[2] + function_pointer(2) + *pointer + + typed_array[1] + qualified_copy + (int)sizeof(array_copy); +} diff --git a/test/parse/cases/gnu_typeof_01_forms.expected b/test/parse/cases/gnu_typeof_01_forms.expected @@ -0,0 +1 @@ +52 diff --git a/test/parse/cases/gnu_typeof_02_alias_and_plain.c b/test/parse/cases/gnu_typeof_02_alias_and_plain.c @@ -0,0 +1,8 @@ +/* Both reserved GNU spellings are extensions. Plain `typeof` deliberately + * remains an ordinary identifier in Kit's C11 language mode. */ +int test_main(void) { + int typeof = 11; + __typeof(typeof) a = 13; + __typeof__(int*) p = &a; + return typeof + *p + 18; +} diff --git a/test/parse/cases/gnu_typeof_02_alias_and_plain.expected b/test/parse/cases/gnu_typeof_02_alias_and_plain.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/gnu_typeof_03_uthash_decltype.c b/test/parse/cases/gnu_typeof_03_uthash_decltype.c @@ -0,0 +1,14 @@ +/* The unmodified uthash DECLTYPE/DECLTYPE_ASSIGN selection used when __GNUC__ + * is advertised. Keep this macro shape as a regression for the upstream + * header, including the pointer-typed assignment expression. */ +#define DECLTYPE(x) (__typeof(x)) +#define DECLTYPE_ASSIGN(dst, src) do { dst = DECLTYPE(dst)(src); } while (0) + +struct item { int value; }; + +int test_main(void) { + struct item item = {42}; + struct item* head = 0; + DECLTYPE_ASSIGN(head, &item); + return head->value; +} diff --git a/test/parse/cases/gnu_typeof_03_uthash_decltype.expected b/test/parse/cases/gnu_typeof_03_uthash_decltype.expected @@ -0,0 +1 @@ +42