commit 7a56910715c61ee826a4c8edeeeaa24ad90ba9bd
parent 3684877141bf927383d6e46b8fd1d2c54dd5af34
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 12 Jun 2026 09:43:21 -0700
fix(macho): sign arm64 executables with 16 KiB code-signature pages
kit ad-hoc-signed Mach-O with 4 KiB hash pages; Apple-Silicon arm64 requires
16 KiB (the native VM page), so the kernel rejected larger arm64 binaries at load
('invalid signature' / SIGKILL) even though codesign -v passed. Carry a per-target
MCtx.cs_page_log2 (14 for KIT_ARCH_ARM_64, 12 for x86_64) and use it for the
CodeDirectory page size, slot count, and hashing.
Diffstat:
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/src/obj/macho/link.c b/src/obj/macho/link.c
@@ -76,6 +76,14 @@
#define CSSLOT_CODEDIRECTORY 0u
#define CS_HASHTYPE_SHA256 2u
#define CS_SHA256_LEN SHA256_DIGEST_LEN
+/* Default code-signing hash page size (log2). Apple's tools sign with a page
+ * size that matches the target's native VM page: 4 KiB (12) on x86_64, but
+ * 16 KiB (14) on Apple-Silicon arm64. The kernel's loader validates the main
+ * executable against its own (16 KiB) pages, so a 4 KiB-page signature is
+ * rejected as invalid on arm64 once the image is large enough to span the
+ * mismatch — small images can slip through, large ones SIGKILL with "invalid
+ * signature". MCtx.cs_page_log2 carries the per-target choice; this is the
+ * x86_64 fallback. */
#define CS_PAGE_SIZE_LOG2 12u
#define CS_EXECSEG_MAIN_BINARY 1u
@@ -302,6 +310,7 @@ typedef struct MCtx {
u32 strtab_off;
u32 codesig_off;
u32 codesig_size;
+ u8 cs_page_log2; /* code-signing hash page size, log2 (12=4K x64, 14=16K arm64) */
u32 nsyms;
u8 uuid[16];
@@ -2217,7 +2226,8 @@ static void wr_u64_be(u8* p, u64 v) {
/* Build the codesig blob with placeholder hashes; size is precise so
* file layout is final after this. */
static void build_codesig_skeleton(MCtx* x, u32 code_limit, const char* ident) {
- u32 code_page = 1u << CS_PAGE_SIZE_LOG2; /* 4096 */
+ u32 page_log2 = x->cs_page_log2 ? x->cs_page_log2 : CS_PAGE_SIZE_LOG2;
+ u32 code_page = 1u << page_log2;
u32 nslots = (code_limit + code_page - 1u) / code_page;
/* CodeDirectory size:
@@ -2260,7 +2270,7 @@ static void build_codesig_skeleton(MCtx* x, u32 code_limit, const char* ident) {
cd[36] = (u8)CS_SHA256_LEN;
cd[37] = (u8)CS_HASHTYPE_SHA256;
cd[38] = 0; /* platform */
- cd[39] = (u8)CS_PAGE_SIZE_LOG2;
+ cd[39] = (u8)page_log2;
wr_u32_be(cd + 40, 0); /* spare2 */
wr_u32_be(cd + 44, 0); /* scatterOffset */
wr_u32_be(cd + 48, 0); /* teamOffset */
@@ -2278,7 +2288,8 @@ static void build_codesig_skeleton(MCtx* x, u32 code_limit, const char* ident) {
static void compute_codesig(MCtx* x, const u8* full_file, u32 file_len_excl_cs,
const char* ident) {
- u32 code_page = 1u << CS_PAGE_SIZE_LOG2;
+ u32 page_log2 = x->cs_page_log2 ? x->cs_page_log2 : CS_PAGE_SIZE_LOG2;
+ u32 code_page = 1u << page_log2;
u32 nslots = (file_len_excl_cs + code_page - 1u) / code_page;
u32 ident_len = (u32)slice_from_cstr(ident).len + 1u;
u8* cd = x->codesig.data + 12 + 8;
@@ -2364,6 +2375,9 @@ void link_emit_macho(LinkImage* img, Writer* w) {
x.w = w;
x.linker = img->linker;
x.link_arch = link_arch_desc_for(img->c);
+ /* Apple-Silicon arm64 mains must be signed with 16 KiB hash pages (the native
+ * VM page); x86_64 uses 4 KiB. See CS_PAGE_SIZE_LOG2. */
+ x.cs_page_log2 = (img->c->target.arch == KIT_ARCH_ARM_64) ? 14u : 12u;
{
const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_MACHO);
x.macho =