This commit is contained in:
bQUARKz 2026-02-20 04:38:51 +00:00
parent a10055f61b
commit b9ac2a98ee
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 122 additions and 50 deletions

View File

@ -212,6 +212,77 @@ impl SyscallIdentity {
} }
} }
/// Resolved syscall information provided to the loader/VM.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SyscallResolved {
/// Numeric syscall id used at runtime (VM executes `SYSCALL <id>` only).
pub id: u32,
/// Associated metadata for verification/runtime checks.
pub meta: SyscallMeta,
}
/// Load-time error for syscall resolution.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadError {
/// The (module, name, version) triple is not known by the host.
UnknownSyscall {
module: &'static str,
name: &'static str,
version: u16,
},
/// The cartridge lacks required capabilities for the syscall.
MissingCapability {
required: CapFlags,
provided: CapFlags,
module: &'static str,
name: &'static str,
version: u16,
},
}
/// Resolve a canonical syscall identity to its numeric id and metadata.
///
/// Returns `None` if the identity is unknown.
pub fn resolve_syscall(module: &'static str, name: &'static str, version: u16) -> Option<SyscallResolved> {
for entry in SYSCALL_TABLE {
if entry.meta.module == module && entry.meta.name == name && entry.meta.version == version {
return Some(SyscallResolved { id: entry.meta.id, meta: entry.meta });
}
}
None
}
/// Resolve all declared program syscalls and enforce capability gating.
///
/// - `declared`: list of canonical identities required by the program.
/// - `caps`: capabilities granted to the cartridge/program.
///
/// Fails deterministically if any syscall is unknown or requires missing caps.
pub fn resolve_program_syscalls(
declared: &[SyscallIdentity],
caps: CapFlags,
) -> Result<Vec<SyscallResolved>, LoadError> {
let mut out = Vec::with_capacity(declared.len());
for ident in declared {
let Some(res) = resolve_syscall(ident.module, ident.name, ident.version) else {
return Err(LoadError::UnknownSyscall { module: ident.module, name: ident.name, version: ident.version });
};
// Capability gating: required must be subset of provided
let missing = res.meta.caps & !caps;
if missing != 0 {
return Err(LoadError::MissingCapability {
required: res.meta.caps,
provided: caps,
module: ident.module,
name: ident.name,
version: ident.version,
});
}
out.push(res);
}
Ok(out)
}
/// Canonical registry of all syscalls and their metadata. /// Canonical registry of all syscalls and their metadata.
/// ///
/// IMPORTANT: This table is the single authoritative source for the slot-based /// IMPORTANT: This table is the single authoritative source for the slot-based
@ -528,4 +599,55 @@ mod tests {
// 3) Table and explicit list sizes must match (guard against omissions). // 3) Table and explicit list sizes must match (guard against omissions).
assert_eq!(SYSCALL_TABLE.len(), all_syscalls().len()); assert_eq!(SYSCALL_TABLE.len(), all_syscalls().len());
} }
#[test]
fn resolver_returns_expected_id_for_known_identity() {
// Pick a stable entry from the table
let id = resolve_syscall("gfx", "clear", 1).expect("known identity must resolve");
assert_eq!(id.id, 0x1001);
assert_eq!(id.meta.module, "gfx");
assert_eq!(id.meta.name, "clear");
assert_eq!(id.meta.version, 1);
}
#[test]
fn resolver_rejects_unknown_identity() {
let res = resolve_syscall("gfx", "nonexistent", 1);
assert!(res.is_none());
// And via the program-level resolver
let requested = [SyscallIdentity { module: "gfx", name: "nonexistent", version: 1 }];
let err = resolve_program_syscalls(&requested, 0).unwrap_err();
match err {
LoadError::UnknownSyscall { module, name, version } => {
assert_eq!(module, "gfx");
assert_eq!(name, "nonexistent");
assert_eq!(version, 1);
}
_ => panic!("expected UnknownSyscall error"),
}
}
#[test]
fn resolver_enforces_capabilities() {
// Choose a syscall that requires GFX caps.
let requested = [SyscallIdentity { module: "gfx", name: "clear", version: 1 }];
// Provide no caps → should fail.
let err = resolve_program_syscalls(&requested, 0).unwrap_err();
match err {
LoadError::MissingCapability { required, provided, module, name, version } => {
assert_eq!(module, "gfx");
assert_eq!(name, "clear");
assert_eq!(version, 1);
assert_ne!(required, 0);
assert_eq!(provided, 0);
}
_ => panic!("expected MissingCapability error"),
}
// Provide correct caps → should pass.
let ok = resolve_program_syscalls(&requested, caps::GFX).expect("must resolve with caps");
assert_eq!(ok.len(), 1);
assert_eq!(ok[0].id, 0x1001);
}
} }

View File

@ -1,53 +1,3 @@
# PR-5.4 — Verifier Integration for Syscall Slot Rules
### Briefing
The verifier must ensure that syscall calls respect argument and return slot counts before runtime.
### Target
* Extend verifier to validate syscall usage.
### Work items
* At syscall call sites:
* Look up `SyscallMeta`.
* Ensure enough argument slots are available.
* Ensure stack shape after call matches `ret_slots`.
* Emit verifier errors for mismatches.
### Acceptance checklist
* [ ] Verifier rejects incorrect syscall slot usage.
* [ ] Correct programs pass.
* [ ] Runtime traps are not required for verifier-detectable cases.
* [ ] `cargo test` passes.
### Tests
* Add tests:
* Too few args for syscall → verifier error.
* Correct args/returns → passes.
### Junie instructions
**You MAY:**
* Extend verifier with syscall checks.
**You MUST NOT:**
* Change runtime trap logic.
* Add new trap categories.
**If unclear:**
* Ask before enforcing slot rules.
---
# PR-5.5 — Remove Legacy Syscall Entry Paths # PR-5.5 — Remove Legacy Syscall Entry Paths
### Briefing ### Briefing