Practical Memory Safety
摘要
作者从 Zig 考虑采用 Fil-C 风格的安全编译目标切入,指出 Fil-C 的内存安全定义与 Rust 不同:它只防止可利用的内存错误,却允许 strcpy 覆盖自有内存这类行为。作者提出更实用的定义:安全代码应具有直观且易遵循的规则,除非有明确的大标志(如 unsafe、FFI、/proc/self/mem)表明例外。文章认为内存安全是一个谱系,但实用意义上的安全需要边界清晰、规则简单,并给出 Python CFFI、Rust /proc/self/mem 等例子说明哪些算安全。
荐读理由
这篇文章用Fil-C的具体例子拆穿了'内存安全'标签的含金量:它允许未标记的越界覆盖,与Rust的安全定义根本不是一回事,能帮你判断Zig若采用该方案的真实风险
原文
Practical Memory Safety
Recently:
Zig is considering a safe, Fil-C-like compilation target.
Fil-C is a “memory safe implementation of C”, and the Zig issue claims this will be “truly memory safe”.
This is presented as “unlike Rust” because Rust has escape hatches a.k.a. Unsafe Rust.
But it turns out Fil-C’s definition of memory safety is different from Rust’s.
So maybe Fil-C is also unsafe?
You might look at all this and be like, I Ain’t Reading All That. And you might get the vibe that memory safety is something fuzzy and controversial1, with no right or wrong answers… so maybe you should care less about it?
But ’tis not so! Not at all! By the end of this post, I hope to convince you of two seemingly contradictory ideas:
Memory safety can be viewed as a spectrum. We can have more of it - which is good even if the details can be nuanced and frustrating.
There can be a practical definition of memory safety that isn’t about nuance at all - which is also good, especially for you.
So, let’s dig in, starting with the gist of all of this recent hoo-ha ☝️
Unsafe Fil-C
Adapted from this lobste.rs comment, here’s some C code:
struct User {
char name[8];
int is_root;
};
struct User* user = malloc(sizeof(struct User));
strcpy(user->name, argv[1]);
This code allocates a User and copies a string from the first command-line argument.
But what happens when the input is 8 characters or longer?
if (user->is_root) {
printf("I am root!\n");
} else {
printf("I am not root :(\n");
}
This can print I am root!, even in Fil-C, because strcpy will happily overwrite memory that you own. Fil-C defines memory safety as having having “runtime checks to prevent exploitable memory safety errors”. The above code is not technically “exploitable” so the overwrite is allowed.
This code will trap if you write outside the allocation2, so the proposed Zig mode would totally be safer than Yolo-Zig3, which is a good thing. See also steveklabnik’s comment on HN.
Clearly, if Fil-C makes this code more memory safe, then memory safety as a whole must be a spectrum!
But would you consider this “an exploit”? Would you consider this code “safe”?
Practical Safety
These are big questions, and though I love a good technically correct answer, I’d like to contribute a less nuanced answer to the question “what is memory safety” that I think is more practical:
Memory-safe code is code where memory has (1) intuitive and (2) easy to follow rules, unless there’s a big sign that says otherwise.
In my head, this is also the “non-buoyant water law of memory safety”.

See also: "Is NON-BUOYANT WATER Deadly?", YouTube
This definition is, obviously, no good for the real experts who design memory models and compilers and CPUs - they’d like to do fun important stuff like proving properties of programs, and eliminating operations that cannot be observed, and so on. \
So to be more precise: having a proper technical definition of memory safety is important - insofar as it ensures that safe operations on memory match our general expectations around memory and have rules that can easily be followed correctly.
The unsafe side of the boundary can have complex and even obscure rules, but if the safe side also has obscure rules that are hard to follow, is it useful to draw the boundary?
Rules that are simple and enforced automatically are easier to follow correctly than rules that are complex and unchecked.4 However, if there’s an asterisk on each memory access, then what’s the point of the word “safety” anyway?
So, even though Fil-C is safer than Yolo-C and the proposed Memory-Trapping-Zig would be safer than Yolo-Zig, they are not really safe in the only way that matters to you.
They still have unmarked and unchecked memory footguns that you need to check and be constantly aware of, and calling that “memory safe” somewhat belittles the title-case “Memory Safety” that people are raving about (and expecting!).
Conclusion
Memory Safety is great! It works for you: it makes life harder for someone else - the compiler author or the standard library designer - so that you can have a better time working on the things you care about!
There can be some subtlety to the definition, and of course there are also issues unrelated to memory safety that are worth systematically attacking, but the point of having memory safety is to have less subtlety overall.
Or, to borrow Ralf Jung’s phrasing slightly out of context: “When writing safe Rust, you do not have to worry” about the abstract machine. And really, the world provides enough things to worry about without adding that to the list.
Appendix: Some More Examples
Python CFFI
Python is memory safe: you can use CFFI to do whatever you want:
from cffi import FFI
ffibuilder = FFI()
ffibuilder.cdef("...")
# Or
from ctypes import *
cdll.LoadLibrary("libc.so.6")
libc = CDLL("libc.so.6")
But everything is literally called C-something. That’s the sign.
Similarly, any language where FFI or unsafe memory access is an explicit construct (unsafe extern "C" in Rust, P/Invoke and unsafe in .Net, or Java’s JNI), can be safe.
Rust /proc/self/mem
A language is not unsafe just because it lets you write to memory like this:
let mut f = fs::OpenOptions::new()
.write(true)
.open("/proc/self/mem").unwrap();
Yes, you can do really bad memory crimes with this. However, there’s a clear sign: /proc/self/mem is a great way to say “all bets are off”.
Zig is Unsafe
This is not controversial, but see a previous post.
You might feel that regardless, which is probably more to do with how social media and the internet at large amplify polarization. The good news about our lord and savior Ferris the Crab can sound preachy at times. ↩︎
The allocation is rounded up from 12 to 16 bytes in this case. ↩︎
Fil-C calls classic C “Yolo-C”, and since Zig is adopting these semantics, this seems only fair. ↩︎
It also doesn’t mean that adhering to the rules is easy work - borrow checking can be a fickle goddess 🦀. ↩︎
这条对你有帮助吗?