[GH-ISSUE #6600] [Bug] CUBIN kernel inspection treats ELF extended section indices as malformed #3987

Closed
opened 2026-10-31 00:45:48 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6600

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

An executed production source-service probe found that a CUBIN kernel whose st_shndx is the ELF escape value SHN_XINDEX is dropped with CUBIN kernel symbol has no executable section. The file carries a valid SHT_SYMTAB_SHNDX companion mapping that symbol to executable section 65280. REA recognizes the CUBIN and its target, but marks the object malformed, reports partial payload coverage, and returns no kernels. A direct-index control returns kernel kernel with complete payload coverage.

The production GpuCodeObjectsService and GpuCodeObjectsProvider ran with real fixture files, admission, hashing, parsing, schema validation, and Evidence construction. No CLI/MCP transport, CUDA compiler, external native-analysis engine, or target execution was run.

Reproduction

With the reported source revision, locked dependencies, and pinned Node.js, save the following as $WORK/probe.mts in an existing disposable directory. Run from the checkout: node node_modules/tsx/dist/cli.mjs "$WORK/probe.mts" "$PWD" "$WORK". The complete probe creates two inert ELF64 CUBIN fixtures and inspects each once through the production shared service.

import {pathToFileURL} from 'node:url';
import {resolve,join} from 'node:path';
import {writeFile} from 'node:fs/promises';
const repo=resolve(process.argv[2] ?? process.cwd());
const work=resolve(process.argv[3] ?? '/tmp');
const load=(name:string)=>import(pathToFileURL(join(repo,name)).href);
const {GpuCodeObjectsProvider}=await load('src/gpu/GpuCodeObjectsProvider.ts');
const {GpuCodeObjectsService}=await load('src/application/gpu/GpuCodeObjectsService.ts');
function cubin(extended:boolean):Buffer {
 const textIndex=extended ? 0xff00 : 6;
 const count=textIndex+1;
 const names=Buffer.from('\0.shstrtab\0.strtab\0.symtab\0.symtab_shndx\0.nv.info.kernel\0.text.kernel\0');
 const strings=Buffer.from('\0kernel\0');
 const syms=Buffer.alloc(48);
 syms.writeUInt32LE(1,24);syms[28]=0x12;syms[29]=0x10;
 syms.writeUInt16LE(extended ? 0xffff : textIndex,30);
 syms.writeBigUInt64LE(16n,40);
 const shndx=Buffer.alloc(8);shndx.writeUInt32LE(extended ? textIndex : 0,4);
 const params=Buffer.from('03190000','hex'); // EIATTR_CBANK_PARAM_SIZE = 0
 const contents=[names,strings,syms,shndx,params,Buffer.alloc(16)];
 let at=64;const offsets=contents.map(b=>{at=Math.ceil(at/8)*8;const p=at;at+=b.length;return p;});
 const table=Math.ceil(at/8)*8;const bytes=Buffer.alloc(table+count*64);
 bytes.set([0x7f,69,76,70,2,1,1,0x33]);bytes.writeUInt16LE(2,16);
 bytes.writeUInt16LE(190,18);bytes.writeUInt32LE(0x81,20);
 bytes.writeBigUInt64LE(BigInt(table),40);bytes.writeUInt32LE(0x500550,48);
 bytes.writeUInt16LE(64,52);bytes.writeUInt16LE(64,58);
 bytes.writeUInt16LE(extended ? 0 : count,60);bytes.writeUInt16LE(1,62);
 if(extended)bytes.writeBigUInt64LE(BigInt(count),table+32);
 const rows=[
  [1,'.shstrtab',3,0,0,1,1],
  [2,'.strtab',3,0,0,1,1],
  [3,'.symtab',2,2,1,8,24],
  [4,'.symtab_shndx',18,3,0,4,4],
  [5,'.nv.info.kernel',0x70000000,3,textIndex,4,0],
  [textIndex,'.text.kernel',1,0,1,16,0],
 ] as const;
 rows.forEach(([index,name,type,link,info,alignment,entry],i)=>{
  contents[i]!.copy(bytes,offsets[i]!);const h=table+index*64;
  bytes.writeUInt32LE(names.indexOf(Buffer.from(name+'\0')),h);
  bytes.writeUInt32LE(type,h+4);bytes.writeBigUInt64LE(index===textIndex?6n:0n,h+8);
  bytes.writeBigUInt64LE(BigInt(offsets[i]!),h+24);
  bytes.writeBigUInt64LE(BigInt(contents[i]!.length),h+32);
  bytes.writeUInt32LE(link,h+40);bytes.writeUInt32LE(info,h+44);
  bytes.writeBigUInt64LE(BigInt(alignment),h+48);bytes.writeBigUInt64LE(BigInt(entry),h+56);
 });return bytes;
}
const provider=new GpuCodeObjectsProvider();const service=new GpuCodeObjectsService(provider);
try {
 for(const extended of [false,true]){
  const label=extended?'extended-index':'direct-index';const bytes=cubin(extended);
  const path=join(work,`gpu-cubin-${label}.cubin`);await writeFile(path,bytes);
  const result=await service.inspect({path,analysis:'kernels',detail:'complete'});
  if(!result.ok)throw result.error;const r=result.value.normalized_result;
  console.log(JSON.stringify({label,size:bytes.length,sections:extended?0xff01:7,coverage:r.coverage,objects:r.objects.map(({inspection,limitations}:any)=>({inspection,limitations})),kernels:r.kernels.map(({name,parameter_count,parameter_bytes}:any)=>({name,parameter_count,parameter_bytes}))}));
 }
}finally{await service.close();}

The control is 672 bytes with 7 sections. The extended case is 4,178,208 bytes with 65,281 sections, below REA's 16 MiB structural and 128 MiB object budgets. It uses e_shnum=0 with the true count in section zero, a CUDA entry symbol with st_shndx=0xffff, and a type-18 companion table linked to the symbol table with entry 1 equal to 0xff00. That destination is a 16-byte-aligned executable SHT_PROGBITS section. Intervening inactive section headers are zeroed. Both fixtures contain the same named kernel and zero-byte parameter declaration. No count/visit limit is approached by the two-symbol table.

Expected behavior

Resolve the kernel symbol's section through its associated extended-index table, then retain its kernel facts. The ELF symbol-table contract places the real index in SHT_SYMTAB_SHNDX whenever st_shndx is SHN_XINDEX; reserved section-index rules require this escape for indices at or above 0xff00. REA documents standalone CUBIN inspection and extracting CUDA entry-symbol declarations. Its native directory owner already accepts extended section counts.

At minimum, an unimplemented standard representation should not be presented as evidence that the supplied executable section is absent or the valid file is malformed.

Impact and scope

The observed effect is loss of the only kernel in the extended-index fixture and incorrect malformed classification. The failure and direct-index control each ran once. Larger compiler/linker outputs can require extended indices; no real CUDA producer was used to establish frequency or which toolkit versions emit this representation. The original binary remains unchanged and the returned result does flag partial coverage. No practical end-user workaround was verified. Reordering sections and rewriting all references was not attempted or established as safe.

REA version

Package 6.7.0; source revision 642f6571ea9d333ea50114c25e648dbb5b8a3c46. The source package version and HEAD were checked locally; the npm registry latest version was checked before the audit. No installed/published CLI launcher was used.

Runtime and platform

Node.js v24.22.1; locked tsx v4.23.15; Linux x86_64, Omarchy 4.0.6, kernel 7.2.8-9-omarchy. No GPU, CUDA producer, Hopper, or Ghidra was involved.

Target and artifact context

Synthetic ELF64 little-endian EM_CUDA (190), CUDA OS ABI 0x33, NVIDIA header version 0x81, sm_80 flags, one STT_FUNC/STO_CUDA_ENTRY symbol, and a numeric zero-byte parameter-area declaration. Its ELF extended-index representation follows the standard linked-table contract; producer/runtime loadability was not tested or claimed.

Interface

Other

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

{"label":"direct-index","size":672,"sections":7,"object_inspection":"complete","payloads":"complete","kernels":[{"name":"kernel","parameter_count":0,"parameter_bytes":0}]}
{"label":"extended-index","size":4178208,"sections":65281,"object_inspection":"malformed","payloads":"partial","kernels":[],"limitation":"CUBIN kernel symbol has no executable section"}

A separate Python byte inspection of the produced files, without invoking REA, established that the extended symbol contains st_shndx=65535, its associated table contains actual index 65280, and that destination has section type 1, flags 0x6, offset 208, and alignment 16. The control points directly to section 6 with the same type, flags, offset, and alignment.

  • Expected source: The linked ELF extended-index contracts and REA CUBIN declaration documentation above.
  • Failure source: The source-service result above and CUBIN symbol lookup, which indexes the section array using the unresolved 16-bit field from readElfSymbol.
  • Evidence provenance: observed. Fixture validity and the cause are separately source-derived.
  • Local verification: reproduced.
  • Reproduction completeness: complete for the production source-service boundary; no CUDA producer or CLI/MCP transport reproduction was run.

Public reachability: the GPU CLI dispatch calls the same shared service used here. The request contains a stable absolute ordinary-file path, analysis: "kernels", detail: "complete", no filters, and no optional digest restriction. Provider admission hashes the file and sends it to the inventory. Native GPU detection identifies machine 190 and reaches the affected parser. Passing the production service's input and output validation rules out a hand-constructed report bypass.

Audit evidence companions: gpu-probe-cubin-xindex.mts, gpu-probe-cubin-xindex.out, and the two generated gpu-cubin-*.cubin fixtures. The inline code recreates them without external artifacts.

Typed outcome or recovery expected

Return kernel kernel with its zero-byte parameter declaration and complete coverage for this supported representation. Proposed restoration check: both fixtures should return the same kernel facts, using the real extended section mapping, while malformed or missing companion tables must remain distinguishable. No fix or restoration was run.

Analysis or suspected cause

Source-derived: raw ELF symbol decoding correctly exposes the 16-bit escape value, but the CUDA consumer never consults its associated SHT_SYMTAB_SHNDX table before treating that value as an actual array index. Extended section-count support does not resolve this separate symbol-reference representation.

Additional context

The initial full open-title index and targeted all-state searches for SHN_XINDEX, CUBIN extended, and GPU symbol/section handling found no matching issue. Broad CUDA/GPU searches found the implemented feature request #5345, not this trigger. Submission-time duplicate searches are performed separately immediately before filing.

AI assistance: this investigation, fixture, and report were prepared with AI assistance. All binary bytes and identifiers are synthetic; no credentials, personal identifiers, or private paths are included. NOT_ENOUGH_INFO: first affected and last known good releases, real-producer prevalence, and a practical workaround remain unknown.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded standard ELF representation loses kernel facts and receives an incorrect malformed classification; the ordinary-index control succeeds and partial coverage is visible.
  • Workaround status: unverified
  • Workaround basis: No safe practical way to obtain REA's kernel report for an existing affected file without rewriting its ELF structure was demonstrated; the small direct-index fixture is a control, not a verified workaround for that file.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6600 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Native analysis or decompilation ## Observed behavior An executed production source-service probe found that a CUBIN kernel whose `st_shndx` is the ELF escape value `SHN_XINDEX` is dropped with `CUBIN kernel symbol has no executable section`. The file carries a valid `SHT_SYMTAB_SHNDX` companion mapping that symbol to executable section 65280. REA recognizes the CUBIN and its target, but marks the object `malformed`, reports partial payload coverage, and returns no kernels. A direct-index control returns kernel `kernel` with complete payload coverage. The production `GpuCodeObjectsService` and `GpuCodeObjectsProvider` ran with real fixture files, admission, hashing, parsing, schema validation, and Evidence construction. No CLI/MCP transport, CUDA compiler, external native-analysis engine, or target execution was run. ## Reproduction With the reported source revision, locked dependencies, and pinned Node.js, save the following as `$WORK/probe.mts` in an existing disposable directory. Run from the checkout: `node node_modules/tsx/dist/cli.mjs "$WORK/probe.mts" "$PWD" "$WORK"`. The complete probe creates two inert ELF64 CUBIN fixtures and inspects each once through the production shared service. ```ts import {pathToFileURL} from 'node:url'; import {resolve,join} from 'node:path'; import {writeFile} from 'node:fs/promises'; const repo=resolve(process.argv[2] ?? process.cwd()); const work=resolve(process.argv[3] ?? '/tmp'); const load=(name:string)=>import(pathToFileURL(join(repo,name)).href); const {GpuCodeObjectsProvider}=await load('src/gpu/GpuCodeObjectsProvider.ts'); const {GpuCodeObjectsService}=await load('src/application/gpu/GpuCodeObjectsService.ts'); function cubin(extended:boolean):Buffer { const textIndex=extended ? 0xff00 : 6; const count=textIndex+1; const names=Buffer.from('\0.shstrtab\0.strtab\0.symtab\0.symtab_shndx\0.nv.info.kernel\0.text.kernel\0'); const strings=Buffer.from('\0kernel\0'); const syms=Buffer.alloc(48); syms.writeUInt32LE(1,24);syms[28]=0x12;syms[29]=0x10; syms.writeUInt16LE(extended ? 0xffff : textIndex,30); syms.writeBigUInt64LE(16n,40); const shndx=Buffer.alloc(8);shndx.writeUInt32LE(extended ? textIndex : 0,4); const params=Buffer.from('03190000','hex'); // EIATTR_CBANK_PARAM_SIZE = 0 const contents=[names,strings,syms,shndx,params,Buffer.alloc(16)]; let at=64;const offsets=contents.map(b=>{at=Math.ceil(at/8)*8;const p=at;at+=b.length;return p;}); const table=Math.ceil(at/8)*8;const bytes=Buffer.alloc(table+count*64); bytes.set([0x7f,69,76,70,2,1,1,0x33]);bytes.writeUInt16LE(2,16); bytes.writeUInt16LE(190,18);bytes.writeUInt32LE(0x81,20); bytes.writeBigUInt64LE(BigInt(table),40);bytes.writeUInt32LE(0x500550,48); bytes.writeUInt16LE(64,52);bytes.writeUInt16LE(64,58); bytes.writeUInt16LE(extended ? 0 : count,60);bytes.writeUInt16LE(1,62); if(extended)bytes.writeBigUInt64LE(BigInt(count),table+32); const rows=[ [1,'.shstrtab',3,0,0,1,1], [2,'.strtab',3,0,0,1,1], [3,'.symtab',2,2,1,8,24], [4,'.symtab_shndx',18,3,0,4,4], [5,'.nv.info.kernel',0x70000000,3,textIndex,4,0], [textIndex,'.text.kernel',1,0,1,16,0], ] as const; rows.forEach(([index,name,type,link,info,alignment,entry],i)=>{ contents[i]!.copy(bytes,offsets[i]!);const h=table+index*64; bytes.writeUInt32LE(names.indexOf(Buffer.from(name+'\0')),h); bytes.writeUInt32LE(type,h+4);bytes.writeBigUInt64LE(index===textIndex?6n:0n,h+8); bytes.writeBigUInt64LE(BigInt(offsets[i]!),h+24); bytes.writeBigUInt64LE(BigInt(contents[i]!.length),h+32); bytes.writeUInt32LE(link,h+40);bytes.writeUInt32LE(info,h+44); bytes.writeBigUInt64LE(BigInt(alignment),h+48);bytes.writeBigUInt64LE(BigInt(entry),h+56); });return bytes; } const provider=new GpuCodeObjectsProvider();const service=new GpuCodeObjectsService(provider); try { for(const extended of [false,true]){ const label=extended?'extended-index':'direct-index';const bytes=cubin(extended); const path=join(work,`gpu-cubin-${label}.cubin`);await writeFile(path,bytes); const result=await service.inspect({path,analysis:'kernels',detail:'complete'}); if(!result.ok)throw result.error;const r=result.value.normalized_result; console.log(JSON.stringify({label,size:bytes.length,sections:extended?0xff01:7,coverage:r.coverage,objects:r.objects.map(({inspection,limitations}:any)=>({inspection,limitations})),kernels:r.kernels.map(({name,parameter_count,parameter_bytes}:any)=>({name,parameter_count,parameter_bytes}))})); } }finally{await service.close();} ``` The control is 672 bytes with 7 sections. The extended case is 4,178,208 bytes with 65,281 sections, below REA's 16 MiB structural and 128 MiB object budgets. It uses `e_shnum=0` with the true count in section zero, a CUDA entry symbol with `st_shndx=0xffff`, and a type-18 companion table linked to the symbol table with entry 1 equal to `0xff00`. That destination is a 16-byte-aligned executable `SHT_PROGBITS` section. Intervening inactive section headers are zeroed. Both fixtures contain the same named kernel and zero-byte parameter declaration. No count/visit limit is approached by the two-symbol table. ## Expected behavior Resolve the kernel symbol's section through its associated extended-index table, then retain its kernel facts. The [ELF symbol-table contract](https://gabi.xinuos.com/elf/05-symtab.html#symbol-table-entry) places the real index in `SHT_SYMTAB_SHNDX` whenever `st_shndx` is `SHN_XINDEX`; [reserved section-index rules](https://gabi.xinuos.com/elf/03-sheader.html#special-section-indexes) require this escape for indices at or above `0xff00`. REA documents [standalone CUBIN inspection](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/docs/gpu-code-objects.md#L3-L7) and extracting [CUDA entry-symbol declarations](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/docs/gpu-code-objects.md#L88-L92). Its [native directory owner already accepts extended section counts](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/native/metadata/NativeSectionDirectory.ts#L100-L119). At minimum, an unimplemented standard representation should not be presented as evidence that the supplied executable section is absent or the valid file is malformed. ## Impact and scope The observed effect is loss of the only kernel in the extended-index fixture and incorrect malformed classification. The failure and direct-index control each ran once. Larger compiler/linker outputs can require extended indices; no real CUDA producer was used to establish frequency or which toolkit versions emit this representation. The original binary remains unchanged and the returned result does flag partial coverage. No practical end-user workaround was verified. Reordering sections and rewriting all references was not attempted or established as safe. ## REA version Package 6.7.0; source revision `642f6571ea9d333ea50114c25e648dbb5b8a3c46`. The source package version and HEAD were checked locally; the npm registry latest version was checked before the audit. No installed/published CLI launcher was used. ## Runtime and platform Node.js v24.22.1; locked tsx v4.23.15; Linux x86_64, Omarchy 4.0.6, kernel 7.2.8-9-omarchy. No GPU, CUDA producer, Hopper, or Ghidra was involved. ## Target and artifact context Synthetic ELF64 little-endian EM_CUDA (190), CUDA OS ABI `0x33`, NVIDIA header version `0x81`, `sm_80` flags, one STT_FUNC/STO_CUDA_ENTRY symbol, and a numeric zero-byte parameter-area declaration. Its ELF extended-index representation follows the standard linked-table contract; producer/runtime loadability was not tested or claimed. ## Interface Other ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence ```json {"label":"direct-index","size":672,"sections":7,"object_inspection":"complete","payloads":"complete","kernels":[{"name":"kernel","parameter_count":0,"parameter_bytes":0}]} {"label":"extended-index","size":4178208,"sections":65281,"object_inspection":"malformed","payloads":"partial","kernels":[],"limitation":"CUBIN kernel symbol has no executable section"} ``` A separate Python byte inspection of the produced files, without invoking REA, established that the extended symbol contains `st_shndx=65535`, its associated table contains actual index 65280, and that destination has section type 1, flags `0x6`, offset 208, and alignment 16. The control points directly to section 6 with the same type, flags, offset, and alignment. - Expected source: The linked ELF extended-index contracts and REA CUBIN declaration documentation above. - Failure source: The source-service result above and [CUBIN symbol lookup](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/gpu/GpuElfKernels.ts#L257-L280), which indexes the section array using the unresolved 16-bit field from [readElfSymbol](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/native/metadata/ElfSymbol.ts#L3-L18). - Evidence provenance: observed. Fixture validity and the cause are separately source-derived. - Local verification: reproduced. - Reproduction completeness: complete for the production source-service boundary; no CUDA producer or CLI/MCP transport reproduction was run. Public reachability: the [GPU CLI dispatch](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/cli/gpuCommands.ts#L99-L118) calls the same shared service used here. The request contains a stable absolute ordinary-file path, `analysis: "kernels"`, `detail: "complete"`, no filters, and no optional digest restriction. [Provider admission](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/gpu/GpuCodeObjectsProvider.ts#L270-L282) hashes the file and sends it to the inventory. [Native GPU detection](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/gpu/GpuInventory.ts#L165-L181) identifies machine 190 and reaches the affected parser. Passing the production service's input and output validation rules out a hand-constructed report bypass. Audit evidence companions: `gpu-probe-cubin-xindex.mts`, `gpu-probe-cubin-xindex.out`, and the two generated `gpu-cubin-*.cubin` fixtures. The inline code recreates them without external artifacts. ## Typed outcome or recovery expected Return kernel `kernel` with its zero-byte parameter declaration and complete coverage for this supported representation. Proposed restoration check: both fixtures should return the same kernel facts, using the real extended section mapping, while malformed or missing companion tables must remain distinguishable. No fix or restoration was run. ## Analysis or suspected cause Source-derived: raw ELF symbol decoding correctly exposes the 16-bit escape value, but the CUDA consumer never consults its associated `SHT_SYMTAB_SHNDX` table before treating that value as an actual array index. Extended section-count support does not resolve this separate symbol-reference representation. ## Additional context The initial full open-title index and targeted all-state searches for `SHN_XINDEX`, `CUBIN extended`, and GPU symbol/section handling found no matching issue. Broad CUDA/GPU searches found the implemented feature request [#5345](https://github.com/morluto/rea/issues/5345), not this trigger. Submission-time duplicate searches are performed separately immediately before filing. AI assistance: this investigation, fixture, and report were prepared with AI assistance. All binary bytes and identifiers are synthetic; no credentials, personal identifiers, or private paths are included. NOT_ENOUGH_INFO: first affected and last known good releases, real-producer prevalence, and a practical workaround remain unknown. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A bounded standard ELF representation loses kernel facts and receives an incorrect malformed classification; the ordinary-index control succeeds and partial coverage is visible. - Workaround status: unverified - Workaround basis: No safe practical way to obtain REA's kernel report for an existing affected file without rewriting its ELF structure was demonstrated; the small direct-index fixture is a control, not a verified workaround for that file.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3987
No description provided.