{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreiganhhxdtg7ka3uuc5l4l2bzutvibck4qqvzfav7j5zm7nrelozuu",
"uri": "at://did:plc:ivbknywyskln22er3nkssdhl/app.bsky.feed.post/3mpqf2sqkgl62"
},
"path": "/t/language-vision-regarding-safety-guarantees/24418?page=2#post_36",
"publishedAt": "2026-07-03T09:34:30.000Z",
"site": "https://internals.rust-lang.org",
"tags": [
"CVE-2024-27308",
"[1]",
"↩︎"
],
"textContent": "alice:\n\n> CVE-2024-27308 is an interesting example\n\nThanks for this concrete example, which exactly demonstrates where Rust fails to properly distinguish between safety and logic.\n\nalice:\n\n> What I mean when I say that \"safe code cannot cause UB\" is that if there's UB, you can always attribute it to an incorrect unsafe block.\n\nThis is imprecise. There's 2 ways to understand \"incorrect unsafe block\":\n\n 1. The unsafe code fails to uphold the safety requirement of some operation. (This is a result-driven meaning of correct, which only takes the goal into account. So \"if the earth is flat, spiders have 10 legs\" is incorrect.)\n 2. The unsafe code[1] is incorrect with respect to its safety contract. (This is the theoretical meaning of correct, which also takes hypotheses into account. So \"if the earth is flat, spiders have 10 legs\" is correct.)\n\n\n\nIn the first case, you're essentially saying \"you can always attribute undefined behavior to where it occurs\". So in that case you just need the unsafe keyword for directly-unsafe code. You don't need the concept of unsafe APIs, because you don't need hypotheses (you ignore them). Because Rust does have the concept of unsafe APIs, this can't be the correct interpretation.\n\nIn the second case, you need to know which hypotheses are available to that unsafe code, which is the notion of safety guarantees (the safety requirements of an unsafe function become safety guarantees for its body). Since in Rust logic contracts are safety guarantees, you can't always attribute undefined behavior to an incorrect unsafe block, it might be that some safe code had a logic bug. So this also can't be the correct interpretation.\n\nThe correct interpretation is: \"safe code cannot cause UB _in its dependencies_ \" (the italic part being implicit). This is why you need unsafe APIs, to prevent safety properties to become logic properties in clients. The notion of safety requirements propagates safety properties from a unit of implementation to its clients.\n\nTo complete the picture (dependency, self, client):\n\n * \"safe code _can_ cause UB in its unit of implementation\": this is by design and why it's useful to have control over what you consider a unit of implementation (crate, module, function). That's the usual understanding of the scope of unsafe.\n * \"safe code _can_ cause UB in its clients\": This is the main concern. With safety guarantees you can prevent safety properties to become logic properties in dependencies, and thus \"safe code _cannot_ cause UB in its clients\".\n\n\n\nalice:\n\n> It had a safety comment saying \"library is implemented correctly\" and, well, it wasn't implemented correctly, so it had an incorrect safety comment. That makes the unsafe block wrong.\n\nThis is again some result-driven meaning of \"[in]correct\". If Rust thinks that \"if the earth is flat then spiders have 10 legs\" is an incorrect statement, then this is a more fundamental issue. And we probably shouldn't even talk about contracts at all (because contracts are implications from requirements to guarantees).\n\nSaying \"library is implemented correctly\" is a hypothesis provided by the language: \"logic contracts are safety guarantees\". (I obviously disagree with this language decision, and this is the purpose of this thread, making sure it's deliberate and not accidental, since it obviously contradicts the statement that \"safe code cannot cause UB\".)\n\nalice:\n\n> So given that, I agree the situation is not 100% clear.\n\nIndeed, which is why I believe neither the library nor the unsafe code is wrong, the language is wrong. The situation is not 100% clear because the language is defect. You don't have this issue in C, because C doesn't try (and fail) to distinguish between logic and safety, it only (successfully) have logic.\n\nalice:\n\n> After all, we _could_ fix the UB by changing the unsafe block too\n\nI think that's another wrong way to look at \"cause UB\"/\"incorrect\". When safety contracts are clear, then there's clearly a culprit: the unit of implementation that is incorrect for safety (sure there can be more than one, but that's not shared attribution, they're all completely wrong). The fact that we can work around a culprit in otherwise not-culprit code, doesn't mean the not-culprit code is culprit.\n\nalice:\n\n> At the time, it was actually argued to me that the CVE should be filed under Tokio because in mio it's just a bug, and it's Tokio that actually triggered UB as a result. However, my take is that this is Rust idealism. There is no other programming language where you would come to the conclusion that the CVE should be filed under Tokio.\n\nI would not call this idealism, I would call this a defect. The fact that logic contracts are safety guarantees is a language defect, and indeed no other programming language made that mistake.\n\nalice:\n\n> This conclusion stems purely from the concept of \"unsafe\" and how closely it's tied with CVEs in our community. Filing it under mio makes much more sense\n\nIndeed, that's what the unsafe mental model tried to fix. You need unsafe code to provide safety guarantees, that's just obvious. And for that you need to be able to choose your safety guarantees (under the constraint that non-`robust` functions should have no safety guarantees, the same way non-`unsafe` functions should have no safety requirements).\n\nalice:\n\n> So while the unsafe code in Tokio did not cause the bug in mio, it did cause the CVE.\n\nThat's an interesting take. I would generalize it as: while a unit of implementation may be incorrect for safety, it may need another (perfectly correct) unit of implementation to demonstrate that incorrectness. I would not say Tokio caused the CVE, I would say Tokio demonstrated a CVE in Mio. (A CVE that only exists because in Rust \"logic contracts are safety guarantees\", maybe Mio never wanted to provide such guarantees, but Rust forced it on them.)\n\nalice:\n\n> Given an instance of UB, the list of causes that came together to trigger UB may include some causes that are entirely safe code. However, if so, then that cannot be the only cause, and one of the other causes will involve unsafe code.\n\nThis is again result-driven thinking. There is no list of causes. There is a list of unsatisfied properties, but the cause is where a post-condition is unsatisfied while its pre-condition were (there may be multiple such causes, but all of them are equally wrong and don't share attribution). This is a trivial statement that at least one unsatisfied property is in unsafe code, because all directly-unsafe code is unsafe code, and undefined behavior only occurs in directly-unsafe code.\n\nalice:\n\n> I do not think wording this as \"safe code cannot cause UB\" is unreasonable.\n\nI hope I managed to explained why I believe the opposite.\n\n* * *\n\n 1. seen as a unit of implementation, or within a unit of implementation ↩︎\n\n\n",
"title": "Language vision regarding safety guarantees"
}