introduce an actually memory safe (unlike Rust) compilation mode inspired by Fil-C #36237
Labels
No labels
abi/f32
abi/ilp32
abi/sf
accepted
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
backend/c
backend/llvm
backend/self-hosted
binutils
breaking
build system
debug info
docs
error message
frontend
fuzzing
incremental
lib/c
lib/compiler-rt
lib/cxx
lib/std
lib/tsan
lib/ubsan-rt
lib/unwind
linking
miscompilation
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
release notes
testing
zig cc
zig fmt
zig reduce
bounty
bug
contributor-friendly
downstream
enhancement
infra
optimization
question
regression
upstream
use case
No assignees
14 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
ziglang/zig#36237
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Based on prior art: Fil-C
Currently Zig has:
This proposal is to keep the existing optimization modes, while introducing an additional ABI ("fil") alongside musl, gnu, msvc, etc.
Related: RFC/Proposal: Turning Zig target triples into quadruples
TODO: contact Fil and ask if he wants make Fil-C ABI stable, versioned, or otherwise. Zig project would prefer to adhere to a standard, if possible.
This ABI has limitations:
Optimize.safeorOptimize.debug. However, the other modes can also work, but for example invisicaps would still be implemented (i.e. runtime pointer provenance checks) because that is needed by the ABI.When using this ABI, Zig compiler would implement the same techniques from the Fil-C project. That is, invisicaps combined with a tight coupling with the target OS in order to wrap all syscalls. No dependency on the Fil-C project; an alternative implementation.
One opportunity to diverge here is garbage collection. Fil pointed out to me that GC (see FUGC) is just one way to implement this technique. Another one would be automatic insertion of unique type ids into invisicaps metadata, ensuring that if UAF does occur, type confusion is still prevented. Unique type IDs are already a necessary ingredient for the accepted proposal add safety checks for pointer casting.
The main challenge to solve is that Zig needs to intercept all syscalls (and in fact all inline assembly), just like Fil-C currently does today. Allocator interface is OK, but
mmaphas to be replaced with wrapper code. I don't think this requires any language changes however. I think it can be done via compiler and standard library changes alone.By doing this, Zig can offer its users an option to compile their projects, including entire tree of C/C++ dependencies, into an executable that is fully memory safe, with no escape hatch, at a ~1-6x performance penalty (depending on prevalence of pointer chasing).
Once this is implemented then, in a twist of fate, Zig will become one of the only toolchains capable of producing actually memory safe executables, especially when you consider that real world applications tend to have C/C++ dependencies and, in the case of Rust, additional use of
unsafebeyond FFI.This seems fundamentally at odds with Zig's philosophy on allocations.
Same here:
(Besides being very inefficient for basically every other architecture, and tricky to implement in an async-signal-safe manner.)
Can you be more specific?
Well unless I'm completely misinterpreting the linked invisicaps page:
I previously wrote a few exploratory spec notes around capability-bounded allocators and temporal memory-safety ideas, including task-scoped arenas, in zig
one question was whether the proposed non-GC approach might also need an allocation generation or object ID to catch same-type address reuse. The notes are still rough, but they may overlap with this proposal. Looking forward to seeing where this goes, Andrew.
Id like a clarification on how this is safer than Rust?
Do I understand it correctly that this is mostly going to affect pointers but if I am using indices / array-access then this could still have next-to-zero overhead?
EDIT: One extra question - is there a chance that this could be enabled ad-hoc with @setRuntimeSafety()? No idea if it's feasible but is that the direction? Or is this rather supposed to be all-or-nothing flag?
I'm really glad that Zig is taking inspiration from Fil-C. However, I think Fil-C has a notable anti-Zig-like pattern, regarding how it defaults to zero-initialisation for everything.
To quote https://fil-c.org/gimso:
My understanding is that Fil-C deviates from usual C semantics by treating "uninitialised" reads to be safe behaviour, because they are technically initialised to zero.
I hope Zig doesn't (blindly) copy this approach, as I find zero-init to be bug prone in that zero is frequently (but not always) a meaningful state, and it's not a no-op, such that a program that relies on or assumes zero-init will have issues on optimised modes where the zero-init would be optimised out. This can break the assumption that optimisation should not introduce bugs, provided there is no illegal behaviour.
If the goal is to possibly depend on Fil-C when compiling C/C++ builds, maybe some extra data needs to track when memory is marked uninitialised or not.
@dynamic_rendering wrote in #36237 (comment):
This would offer no escape hatches. (E.g., no
unsafekeyword.)So, if I'm understanding this correctly: instead of performing a singular, one time compile check, we are offloading the entire issue to runtime...?
@Barry1375 wrote in #36237 (comment):
You must be quite detached from the project and looking at it from the outside you might think like this, but Andrew is interested in Fil-C way before than this "bun rewrite" thing, sharing links in the IRC channel months ago.
So an all-or-nothing scheme?
@yrds wrote in #36237 (comment):
well, the Bun rewrite could move this idea up on his TODO list and provide fuel for Andrew's flamy tongue. I totally get the emotions, but please, someone, prohibit flaming and "Holy Wars" under Code of Conduct. the proposal is... curious, if a little contrary to Zig's stated philosophy, but it could totally be written without snark that obscures the technical beauty.
For reasons stated by others, this feels at-odds with Zig idiom and more an unnecessary "box-checking" to perhaps technically "win one over" on Rust, which in my view should not be a motivation for expansion.
@ashwinin wrote in #36237 (comment):
The point is to not misbehave when a memory safety error occurs—that is, you end up with no exploitable memory safety errors. Rust still has these because of the
unsafekeyword. Basically the difference is compile-time memory safety (what Rust offers if you don't use anyunsafeblocks) and run-time memory safety (what Fil-C offers).I suppose this ABI's main purpose is to be used for fuzzing (and generally for testing), to ensure that some inspected program has no Illegal Behaviour - akin to Rust's MIRI (though perhaps with better FFI support).
From my understanding Fil-C does not completely adhere to C's notions of UB: some behaviours which are considered Undefined in the spec are allowed by Fil-C (e.g. signed integer overflow, access of uninitialised memory, some memory reinterpretations). Other behaviours, which are fine according to the C spec seem to be disallowed in Fil-C (e.g. in some cases, having more than 16 (but less than 128) arguments in a function call). My question then, is whether this ABI is intended to check for every occurrence of IB, and never report false positives; or, like Fil-C, declare that some Illegal Behaviours will manifest as some safe operation at runtime?
This is a very big change. I think the main things to consider are the stability of the ABI and if is compatible with the Zig philosophy.
Besides that, I'm excited to see how this goes.