[HN Gopher] How safe is Zig?
___________________________________________________________________
How safe is Zig?
Author : orf
Score : 177 points
Date : 2022-06-23 15:19 UTC (7 hours ago)
(HTM) web link (www.scattered-thoughts.net)
(TXT) w3m dump (www.scattered-thoughts.net)
| nwellnhof wrote:
| UBSan has a -fsanitize-minimal-runtime flag which is supposedly
| suitable for production:
|
| https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#...
|
| So it seems that null-pointer dereferences and integer overflows
| can be checked at runtime in C. Besides, there should be
| production-ready C compilers that offer bounds checking.
| pjmlp wrote:
| There should but there aren't, GCC had a couple of extensions
| on a branch like 20 years ago that never got merged.
|
| The best is to use C++ instead, with bounds checked library
| types.
| uecker wrote:
| You can already get some bounds checking, although more work
| is needed:
|
| https://godbolt.org/z/abx7KE44z
| pjmlp wrote:
| Yeah, indeed. Thanks for sharing it.
| pjmlp wrote:
| This is why for me, Zig is mostly a Modula-2 with C syntax in
| regards to safety.
|
| All the runtime tooling it offers, already exists for C and C++
| for at least 30 years, going back to stuff like Purify (1992).
| lmh wrote:
| Question for Zig experts:
|
| Is it possible, in principle, to use comptime to obtain Rust-like
| safety? If this was a library, could it be extended to provide
| even stronger guarantees at compile time, as in a dependent type
| system used for formal verification?
|
| Of course, this does not preclude a similar approach in Rust or
| C++ or other languages; but comptime's simplicity and generality
| seem like they might be beneficial here.
| avgcorrection wrote:
| Why would the mere existence of some static-eval capability
| give you that affordance?
|
| Researchers have been working on these three things for
| decades. Yes, "comptime" isn't some Zig invention but a
| somewhat limited (and anachronistic to a degree) version of
| what researchers have added to research versions of ML and
| Ocaml. So can it implement all the static language goodies of
| Rust _and_ give you dependent types? Sure, why not? After all,
| computer scientists never had the idea that you can evaluate
| values and types at compile-time. Now all those research papers
| about static programming language design will wither on their
| roots now that people can just use the simplicity and
| generality of `comptime` to prove programs correct.
| anonymoushn wrote:
| It is possible in principle to write a Rust compiler in
| comptime Zig, but the real answer is "no."
| kristoff_it wrote:
| Somebody implemented part of it in the past, but it was based
| on the ability to observe the order of execution of comptime
| blocks, which is going to be removed from the language
| (probably already is).
|
| https://github.com/DutchGhost/zorrow
|
| It's not a complete solution, among other things, because it
| only works if you use it to access variables, as the language
| has no way of forcing you.
| ptato wrote:
| Not an expert by any means, but my gut says that it would be
| very cumbersome and not practical for general use.
| pron wrote:
| Not as it is (it would require mutating the type's "state"),
| but hypothetically, comptime could be made to support even more
| programmable types. But could doesn't mean should. Zig values
| language simplicity and explicitness above many other things.
| dleslie wrote:
| And here is the table with Nim added; though potentially many
| GC'd languages would be similar to Nim:
|
| https://uploads.peterme.net/nimsafe.html
|
| Edit: noteworthy addendum: the ARC/ORC features have been
| released, so the footnote is now moot.
| 3a2d29 wrote:
| Seeing Nim danger made me think, shouldn't rust unsafe be
| added?
|
| Seems inaccurate to display rust as safe and not include what
| actually allows memory bugs to be found in public crates.
| jewpfko wrote:
| Thanks! I'd love to see a Dlang BetterC column too
| Snarwin wrote:
| Here's a version with D included:
|
| https://gist.github.com/pbackus/0e9c9d0c83cd7d3a46365c054129.
| ..
|
| The only difference in BetterC is that you lose access to the
| GC, so you have to use RC if you want safe heap allocation.
| IshKebab wrote:
| I don't know why Rust gets "runtime" and Nim gets "compile
| time" for type confusion?
| shirleyquirk wrote:
| yes, for tagged unions specifically, (which the linked post
| refers to for that row) Nim raises an exception at runtime
| when trying to access the wrong field, (or trying to change
| the discriminant)
| ArrayBoundCheck wrote:
| I like zig but this is taking a page out of rust book and
| exaggerating C and C++
|
| clang and gcc will both tell you at runtime if you go out of
| bounds, have an integer overflow, use after free etc. You need to
| turn on the sanitizer. You can't have them all on at the same
| time because code will be unnecessarily slow (ex: having thread
| sanitizer on in a single threaded app is pointless)
| lijogdfljk wrote:
| What is the cause of all those notorious C bugs then?
| CodeSgt wrote:
| > at runtime
| [deleted]
| kubanczyk wrote:
| Whoa the username checks out perfectly.
| ArrayBoundCheck wrote:
| Haha yes. I love knowing I'm in bounds but unfortunately
| saying anything about C++ (that isn't a criticism) is out of
| bounds and my comment got downvoted enough that I don't feel
| like saying more
| masklinn wrote:
| > clang and gcc will both tell you at runtime if you go out of
| bounds [...] You can't have them all on at the same time
| because code will be unnecessarily slow
|
| Yeah, so clang and gcc don't actually tell you at runtime if
| you go out of bounds. How many program ship production binaries
| with asan or ubsan enabled, to say nothing of msan or tsan?
|
| Also you can't have them all on at the same time because
| they're not necessarily compatible with one another[0], you
| literally can't run with both asan and msan, or asan and tsan.
|
| [0] https://github.com/google/sanitizers/issues/1039
| pjmlp wrote:
| Quite a few subsystems on Android, but that is about it.
|
| https://source.android.com/devices/tech/debug/hwasan
| woodruffw wrote:
| Neither Clang nor GCC has perfect bounds or lifetime analysis,
| since the language semantics forbid it: it's perfectly legal at
| compile time to address at some offset into a supplied pointer,
| because the compiler has no way of knowing that the memory
| there _isn 't_ owned and initialized.
|
| Sanitizers are great; I _love_ sanitizers. But you can 't run
| them in production without a significant performance hit, and
| that's where they're needed most. I don't believe this post
| blows that problem out of proportion, and is correct in noting
| that we can solve it without runtime instrumentation and
| overhead.
| AlotOfReading wrote:
| State of the art sanitizing is pretty consistently in the
| <50% overhead range (e.g. SANRAZOR), with things like UBSAN
| coming in under 10%. If you can't afford even that, tools
| like ASAP have been around for 7-ish years now to make
| overhead arbitrarily low by trading off increased false-
| negatives in hot codepaths.
|
| Yes, the "just-enable-the-compiler-flags" approach can be
| expensive, but the tools exist to allow most people to be
| sanitizing most of the time. Devs simply don't know what's
| available to them.
| woodruffw wrote:
| I'd consider even 10% to be a significant performance hit.
| People scream bloody murder when CPU-level mitigations
| cause even 1-2% regressions. The marginal cost of
| mitigations when memory safe code can run without them is
| infinite.
|
| But let's say, for the sake of argument, that I can
| tolerate programs that run twice as long in production.
| This doesn't improve much:
|
| * I'm not going to be deploying SoTA sanitizers (SANRAZOR
| is currently a research artifact; it's not available in
| mainline LLVM as far as I can tell.)
|
| * No sanitizer that I know of _guarantees_ that execution
| corresponds to memory safety. ASan famously won 't detect
| reads of uninitialized memory (MSan will, but you can't use
| both at the same time), and it similarly won't detect
| layout-adjacent overreads/writes.
|
| That's a lot of words to say that I think sanitizers are
| great, but they're not a meaningful alternative to actual
| memory safety. Not when I can have my cake and eat it too.
| AlotOfReading wrote:
| I think we basically agree. Hypothetically ideal memory
| safety is strictly better, but sanitizers are better than
| nothing for code using fundamentally unsafe languages. My
| personal experience is that more people are dissuaded
| from sanitizer usage more by hypothetical (and
| manageable) issues like overhead than real implementation
| problems.
| KerrAvon wrote:
| If you can afford a 10-50% across-the-board performance
| reduction, why would you not use a higher-level, actually
| safe language like Ruby or Python? Remember that the
| context of this article is Zig vs other languages, so the
| assumption is you're writing new code.
| slowking2 wrote:
| Python is usually a lot more than a 50% reduction in
| performance. Sometimes you need better performance but
| not the best performance.
| AlotOfReading wrote:
| I work in real time, often safety critical environments.
| High level interpreted languages aren't particularly
| useful there. The typical options are C/C++, hardware
| (e.g. FPGAs), or something more obscure like Ada/Spark.
|
| But in general, sanitizers are also something you can do
| to _legacy code_ to bring it closer to safety and you can
| turn them off for production if you absolutely,
| definitely need those last few percent (which few people
| do). It 's hard to overstate how valuable all of that is.
| A big part of the appeal of zig is its interoperability
| with C and the ability to introduce it gradually. Compare
| to the horrible contortions you have to do with CFFI to
| call Python from C.
| anonymoushn wrote:
| For Ruby or Python I think you'll be paying more than 90%
| anonymoushn wrote:
| > People scream bloody murder when CPU-level mitigations
| cause even 1-2% regressions
|
| For a particular simulation on a particular Cascade Lake
| chip, mitigations collectively cause it to run about 30%
| slower. So I won't scream about 1%, but that's a lot of
| 1%s.
| ArrayBoundCheck wrote:
| > I'd consider even 10% to be a significant performance
| hit. People scream bloody murder when CPU-level
| mitigations cause even 1-2% regressions. The marginal
| cost of mitigations when memory safe code can run without
| them is infinite.
|
| What people? and in my experience rust has always been
| much higher than 2% regression
| com2kid wrote:
| > it's perfectly legal at compile time to address at some
| offset into a supplied pointer, because the compiler has no
| way of knowing that the memory there isn't owned and
| initialized.
|
| Embedded land, everything is a flat memory map, odds are
| malloc isn't used at all, memory is possibly 0'd on boot.
|
| It is perfectly valid to just start walking all over memory.
| You have a bunch of #defines with known memory addresses in
| them and you can just index from there.
|
| Fun fact: Microsoft Band writes crash dumps to a known
| location in SRAM and because SRAM doesn't instantly lose its
| contents on reboot, after a crash the runtime checks for
| crash dump data at that known address and if present would
| upload the crash dump to servers for analysis and then 0 out
| that memory.[1]
|
| Embedded rocks!
|
| [1] There is a bit more to it to ensure we aren't just
| reading random data after along power off, but I wasn't part
| of the design, I just benefited from a 256KB RAM wearable
| having crash dumps that we could download debugging symbols
| for.
| xedrac wrote:
| > So I'm not covering tools like AddressSanitizer that are
| intended for testing and are not recommended for production
| use.
|
| How is it an exaggeration when he explicitly called this out?
| ArrayBoundCheck wrote:
| ASAN isn't just "for testing". A lot of people went straight
| to the chart (like me) and it reeks of bullshit. double free
| is the same as use after free, null pointer dereference is
| essentially the same as type confusion since a nullable
| pointer is confused with a non null pointer, invalid stack
| read/write is the same as array out of bounds (or invalid
| pointers), etc
|
| I also never heard of a data race existing without a race
| condition existing. That's a pointless metric like many of
| the above I mentioned
| hyperpape wrote:
| Can you explain why, in spite of the fact that (according to
| you) C & C++ aren't that unsafe, critical projects like
| Chromium can't get this right?
| https://twitter.com/pcwalton/status/1539112080590217217
|
| Is the Project Zero team just too lazy to remind Chromium to
| use sanitizers?
| uecker wrote:
| I think the big question is, whether two teams writing
| software on a fixed budget using Rust or C using modern tools
| and best practices would end up with a safer product. I think
| this is not clear at all.
| pcwalton wrote:
| People have done just that with, for example, Firefox
| components and found that yes, Rust gives you a safer
| product.
| uecker wrote:
| Do you have a pointer? I know they rewrote Firefox
| components, but I am not aware of a real study with a 1:1
| comparison.
| uecker wrote:
| (Ok, I should read the text before sending.)
| jerf wrote:
| While I'm generally in favor of the proposition that C++ is
| an intrinsically dangerous language, pointing at one of the
| largest possible projects that uses it isn't the best
| argument. If I pushed a button and magically for free Chrome
| was suddenly in 100% pure immaculate Rust, I'm sure it would
| still have many issues and problems that few other projects
| would have, just due to its sheer scale. I would still
| consider it an open question/problem as to whether Rust can
| scale up to that size and still be something that humans can
| modify. I could make a solid case that the difficulty of
| working in Rust would very accurately reflect a true and
| essential difficulty of working at that scale in general, but
| it could still be a problem.
|
| (Also Rust defenders please note I'm not saying Rust _can 't_
| work at that scale. I'm just saying, it's a very big scale
| and I think it's an open problem. My personal opinion and gut
| say yes, it shouldn't be any worse than it has to be because
| of the sheer size (that is, the essential complexity is
| pretty significant no matter what you do), but I don't _know_
| that.)
| hyperpape wrote:
| You're right that Chromium* is a very difficult task, but I
| disagree with the conclusion you draw. I think Chromium is
| one of the best examples we can consider.
|
| There would absolutely be issues, including security
| issues. But there is also very good evidence that the
| issues that are most exploited in browsers and operating
| systems relate to memory safety. Alex Gaynor's piece that
| the author linked is good on this point.
|
| While securing Chromium is huge and a difficult task, it
| and consumer operating systems are crucial for individual
| security. Until browsers and consumer operating systems are
| secure, individuals ranging from persecuted political
| dissidents to Jeff Bezos won't be secure.
|
| * Actually not sure why I said Chromium rather than Chrome.
| Nothing hangs on the distinction, afaict.
| ArrayBoundCheck wrote:
| Considering how much I got downvoted no I don't want to
| comment more about this. But I'll let you ponder why while
| using rust has you could get a use after free sometimes
| https://cve.mitre.org/cgi-
| bin/cvename.cgi?name=CVE-2021-4572...
| hyperpape wrote:
| Here's the commit: https://github.com/jeromefroe/lru-
| rs/pull/121/commits/416a2d....
|
| I don't think this does much for your initial claim. Take
| the most generous reading you can--Rust isn't any better at
| preventing UAF than C/C++. That doesn't make safe C/C++ a
| thing, it means that Rust isn't an appropriate solution.
| alfiedotwtf wrote:
| > Rust isn't any better at preventing UAF than C/C++
|
| Maybe I'm missing something here?
| ArrayBoundCheck wrote:
| You missed the point. Just like the author did when he
| disqualified all the C++ tools
|
| Writing unsafe code and removing tools "because
| production" gets you unsafe code as shown in that rust
| cve
| XelNika wrote:
| With Zig and Rust you have to explicitly opt-out with
| `ReleaseFast` and `unsafe` respectively, that makes a big
| difference. Rust has the added safety that you cannot (to
| my knowledge at least) gain performance by opting out
| with a flag at compile-time, it has to be done with
| optimized `unsafe` blocks directly in the code.
|
| Lazy C++ is unsafe, lazy Zig is safe-ish, lazy Rust is
| safe. Given how lazy most programmers are, I consider
| that a strong argument against C++.
| ArrayBoundCheck wrote:
| You didn't seem to click the commit the guy linked with
| the rust code https://github.com/jeromefroe/lru-
| rs/pull/121/commits/416a2d...
|
| It has nothing to do with opting out. Zig, Rust and no
| language saves you when you write incorrect unsafe code.
| My original point is disqualifying c tools is misleading
| and everything suffers from incorrect unsafe code
| Arnavion wrote:
| >It has nothing to do with opting out.
|
| It does. The original code compiled because the borrow is
| computed using `unsafe`. That `unsafe` is the opt-out.
|
| >Zig, Rust and no language saves you when you write
| incorrect unsafe code. My original point is disqualifying
| c tools is misleading and everything suffers from
| incorrect unsafe code
|
| And the other people's point is that if one language
| defaults to writing unsafe code and the other language
| requires opting out of safety to write unsafe code, then
| the second language has merit over the first.
| wyldfire wrote:
| One interesting distinction is that it sounds as if - for Zig,
| this is a language feature and not a toolchain feature.
| Although if there's only one toolchain for zig maybe that's a
| distinction-without-a-difference. At least it's not opt-in,
| that's really nice. Believe it or not, there are lots of people
| who write and debug C/C++ code who don't know about sanitizers
| or they know about it and never decide to use them.
| throwawaymaths wrote:
| I think it would be interesting to see zig move towards
| annotation-based compile time lifetime checking plugin
| (ideally in-toolchain, but alternatively as a library). You
| could choose to turn it on selectively for security-critical
| pathways, turn it off for "trust me" functions, or, do it on
| "not every recompilation", as desired.
| pjmlp wrote:
| The irony being that lint exists since 1979, and already
| using a static analyser would be a bing improvement in some
| source bases.
| woodruffw wrote:
| This was a great read, with an important point: there's always a
| tradeoff to be made, and we can make it (e.g. never freeing
| memory to obtain temporal memory safety without static lifetime
| checking).
|
| One thought:
|
| > Never calling free (practical for many embedded programs, some
| command-line utilities, compilers etc)
|
| This works well for compilers and embedded systems, but please
| don't do it command-line tools that are meant to be scripted
| against! It would be very frustrating (and a violation of the
| pipeline spirit) to have a tool that works well for `N`
| independent lines of input but not `N + 1` lines.
| avgcorrection wrote:
| > This was a great read, with an important point: there's
| always a tradeoff to be made, and we can make it (e.g. never
| freeing memory to obtain temporal memory safety without static
| lifetime checking).
|
| I.e. we can choose to risk running out of memory? I don't
| understand how this is a viable strategy unless you know you
| only will process a certain input size.
| samatman wrote:
| There are some old-hand approaches to this which work out fine.
|
| An example would be a generous rolling buffer, with enough room
| for the data you're working on. Most tools which are working on
| a stream of data don't require much memory, they're either
| doing a peephole transformation or building up data with
| filtration and aggregation, or some combination.
|
| You can't have a use-after-free bug if you never call free,
| treating the OS as your garbage collector for memory (not other
| resources please) is fine.
| woodruffw wrote:
| Yeah, those are the approaches that I've used (back when I
| wrote more user tools in C). I wonder how those techniques
| translate to a language like Zig, where I'd expect the naive
| approach to be to allocate a new string for each line/datum
| (which would then never truly be freed, under this model.)
| anonymoushn wrote:
| I've been writing a toy `wordcount` recently, and it seems like
| if I wanted to support inputs much larger than the ~5GB file
| I'm testing against, or inputs that contain a lot more unique
| strings per input file size, I would need to realloc, but I
| would not need to free.
| woodruffw wrote:
| Is that `wordcount` in Zig? My understanding (which could be
| wrong) is that reallocation in Zig would leave the old buffer
| "alive" (from the allocator's perspective) if it couldn't be
| expanded, meaning that you'd eventually OOM if a large enough
| contiguous region couldn't be found.
| anonymoushn wrote:
| It's in zig but I just call mmap twice at startup to get
| one slab of memory for the whole file plus all the space
| I'll need. I am not sure whether Zig's
| GeneralPurposeAllocator or PageAllocator currently use
| mremap or not, but I do know that when realloc is not
| implemented by a particular allocator, the Allocator
| interface provides it as alloc + memcpy + free. So I think
| I would not OOM. In safe builds when using
| GeneralPurposeAllocator, it might be possible to exhaust
| the address space by repeatedly allocating and freeing
| memory, but I wouldn't expect to run into this on accident.
| woodruffw wrote:
| That's interesting, thanks for the explanation!
| dundarious wrote:
| They don't (at least the GPA's defaulting backing
| allocator is the page_allocator, which doesn't). https://
| github.com/ziglang/zig/blob/master/lib/std/heap.zig
| avgcorrection wrote:
| A meta point to make here but I don't quite understand the
| pushback that Rust has gotten. How often does a language come
| around that flat out eliminates certain errors statically, and at
| the same time manages to stay in that low-level-capable pocket?
| _And_ doesn't require a PhD (or heck, a scholarly stipend) to
| use? Honestly that might be a once in a lifetime kind of thing.
|
| But not requiring a PhD (hyperbole) is not enough: it should be
| Simple as well.
|
| But unfortunately Rust is ( _mamma mia_ ) Complex and only
| pointy-haired Scala type architects are supposed to gravitate
| towards it.
|
| But think of what the distinction between no-found-bugs (testing)
| and no-possible-bugs (a certain class of bugs) buys you; you
| don't ever have to even think about those kinds of things as long
| as you trust the compiler and the Unsafe code that you rely on.
|
| Again, I could understand if someone thought that this safety was
| not worth it if people had to prove their code safe in some
| esoteric metalanguage. And if the alternatives were fantastic.
| But what are people willing to give up this safety for? A whole
| bunch of new languages which range from improved-C to high-level
| languages with low-level capabilities. And none of them seem to
| give some alternative iron-clad guarantees. In fact, one of their
| _selling point_ is mere optionality: you can have some safety and
| /or you can turn it off in release. So runtime checks which you
| might (culturally/technically) be encouraged to turn off when you
| actually want your code to run out in the wild, where users give
| all sorts of unexpected input (not just your "asdfg" input) and
| get your program into weird states that you didn't have time to
| even think of. (Of course Rust does the same thing with certain
| non-memory-safety bug checks like integer overflow.)
| the__alchemist wrote:
| This is a concise summary of why I'm betting on Rust as the
| future of performant and embedded computing. You or I could
| poke holes in it for quite some time. Yet, I imagine the holes
| would be smaller and less numerous than in any other language
| capable in these domains.
|
| I think some of the push back is from domains where Rust isn't
| uniquely suited. Eg, You see a lot of complexity in Rust for
| server backends; eg async and traits. So, someone not used to
| Rust may see these, and assume Rust is overly complex. In these
| domains, there are alternatives that can stand toe-to-toe with
| it. In lower-level domains, it's not clear there are.
| cogman10 wrote:
| > I think some of the push back is from domains where Rust
| isn't uniquely suited. Eg, You see a lot of complexity in
| Rust for server backends; eg async and traits. So, someone
| not used to Rust may see these, and assume Rust is overly
| complex. In these domains, there are alternatives that can
| stand toe-to-toe with it. In lower-level domains, it's not
| clear there are.
|
| The big win for rust in these domains is startup time, memory
| usage, and distributable size.
|
| It may be that these things outweigh the easier programming
| of go or java.
|
| Now if you have a big long running server with lots of
| hardware at your disposal then rust doesn't make a whole lot
| of sense. However, if want something like an aws lambda or
| rapid up/down scaling based on load, rust might start to look
| a lot more tempting.
| dilap wrote:
| What Rust does is incredibly cool and impressive.
|
| But as someone that's dabbled a bit in both Zig and Rust, I
| think there's a lot of incidental complexity in Rust.
|
| For example, despite having used them and read the docs, I'm
| still not exactly sure how namespaces work in Rust. It takes
| 30s to understand exactly what is going on in Zig.
| kristoff_it wrote:
| > Of course Rust does the same thing with certain non-memory-
| safety bug checks like integer overflow.
|
| The problem with getting lost too much in the ironclad
| certainties of Rust is that you start forgetting that
| simplicity ( _papa pia_ ) protects you from other problems. You
| can get certain programs in pretty messed up states with an
| unwanted wrap around.
|
| Programming is hard. Rust is cool, very cool, but it's not a
| universal silver bullet.
| avgcorrection wrote:
| Nothing Is Perfect is a common refrain and non-argument.
|
| If option A has 20 defects and option B has the superset of
| 25 defects then option A is better--the fact that option A
| has defects at all is completely besides the point with
| regards to relative measurements.
| coldtea wrote:
| > _If option A has 20 defects and option B has the superset
| of 25 defects then option A is better_
|
| Only if "defect count" is what you care for.
|
| What if you don't give a fuck about defect count, but
| prefer simplicity to explore/experiment quickly, ease of
| use, time to market, and so on?
| Karrot_Kream wrote:
| But if Option A has 20 defects and takes a lot of effort to
| go down to 15 defects, yet Option B has 25 defects and
| offers a quick path to go down to 10 defects, then which
| option is superior? You can't take this in isolation. The
| cognitive load of Rust takes a lot of defects out of the
| picture completely, but going off the beaten path in Rust
| takes a lot of design and patience.
|
| People have been fighting this fight forever. Should we use
| static types which make it slower to iterate or dynamic
| types that help converge on error-free behavior with less
| programmer intervention? The tradeoffs have become clearer
| over the years but the decision remains as nuanced as ever.
| And as the decision space remains nuanced, I'm excited
| about languages exploring other areas of the design space
| like Zig or Nim.
| avgcorrection wrote:
| > But if Option A has 20 defects and takes a lot of
| effort to go down to 15 defects, yet Option B has 25
| defects and offers a quick path to go down to 10 defects,
| then which option is superior?
|
| Yes. If you change the entire premise of my example then
| things are indeed different.
|
| Rust eliminates some defects entirely. Most other low-
| level languages do not. You would have to use a language
| like ATS to even compete.
|
| That's where the five-less-defects thing comes from.
|
| Go down to ten effects? What are you talking about?
| kristoff_it wrote:
| Zig keeps overflow checks in the main release mode
| (ReleaseSafe), Rust defines ints as naturally wrapping in
| release. This means that Rust is not a strict superset of
| Zig in terms of safety, if you want to go down that route.
|
| I personally am not interested at all in abstract
| discussions about sets of errors. Reality is much more
| complicated, each error needs to be evaluated with regards
| to the probability of causing it and the associated cost.
| Both things vary wildly depending on the project at hand.
| avgcorrection wrote:
| > This means that Rust is not a strict superset of Zig in
| terms of safety, if you want to go down that route.
|
| Fair.
|
| > I personally am not interested at all in abstract
| discussions about sets of errors.
|
| Abstract? Handwaving "no silver bullet" is even more
| abstract (non-specific).
| einpoklum wrote:
| One-liner summary: Zig has run-time protection against out-of-
| bounds heap access and integer overflow, and partial run-time
| protection against null pointer dereferencing and type mixup (via
| optionals and tagged unions); and nothing else.
| tptacek wrote:
| "Temporal" and "spatial" is a good way to break this down, but it
| might be helpful to know the subtext that, among the temporal
| vulnerabilities, UAF and, to an extent, type confusion are the
| big scary ones.
|
| Race conditions are a big ugly can of worms whose exploitability
| could probably be the basis for a long, tedious debate.
|
| When people talk about Zig being unsafe, they're mostly reacting
| to the fact that UAFs are still viable in it.
| jorangreef wrote:
| I see your UAF and raise you a bleed!
|
| As you know, buffer bleeds like Heartbleed and Cloudbleed can
| happen even in a memory safe language, they're hard to defend
| against (padding is everywhere in most formats!), easier to
| pull off than a UAF, often remotely accessible, difficult to
| detect, remain latent for a long time, and the impact is
| devastating. All your RAM are belong to us.
|
| For me, this can of worms is the one that sits on top of the
| dusty shelf, it gets the least attention, and memory safe
| languages can be all the more vulnerable as they lull one into
| a false sense of safety.
| kaba0 wrote:
| Would that work in the case of Java for example? It nulls
| every field as per the specification (at least observably at
| least), so unless someone writes some byte mangling manually
| I don't necessarily see it work out.
| tptacek wrote:
| Has an exploitable buffer bleed (I'm happy with this
| coinage!) happened in any recent memory safe codebase?
| jorangreef wrote:
| I worked on a static analysis tool to detect bleeds in
| outgoing email attachments, looking for non-zero padding in
| the ZIP file format.
|
| It caught different banking/investment systems written in
| memory safe languages leaking server RAM. You could
| sometimes see the whole intranet web page, that the teller
| or broker used to generate and send the statement, leaking
| through.
|
| Bleeds terrify me, no matter the language. The thing with
| bleeds is that they're as simple as a buffer underflow, or
| forgetting to zero padding. Not even the borrow checker can
| provide safety against that.
| raphlinus wrote:
| I am skeptical until I see the details, and strongly
| suspect you are dealing with a "safe-ish" language rather
| than one which has Rust-level guarantees. Uninitialized
| memory reads are undefined behavior in basically all
| memory models in the C tradition. In Rust it is not
| possible to make a reference to a slice containing
| uninitialized memory without unsafe (and the rules around
| this have tightened relatively recently, see
| MaybeUninit).
|
| I say this as someone who is doing a lot of unsafe for
| graphics programming - I want to be able to pass a buffer
| to a shader without necessarily having zeroed out all the
| memory, in the common case I'm only using some of that
| buffer to store the scene data etc. I have a safe-ish
| abstraction for this (BufWriter in piet-gpu, for the
| curious), but it's still possible for unsafe shaders to
| do bad things.
| ghusbands wrote:
| I would imagine that the scenario is simply reuse of some
| buffer without clearing it, maybe in an attempt to save
| on allocations. It can happen across so many (even safe)
| languages. It doesn't matter what guarantees you have
| around uninitialised memory if you're reusing an
| initialised buffer yourself.
| jorangreef wrote:
| Hackers exploit any avenue (and usually come in through
| the basement!), regardless of how skeptical we might be
| that they won't. They don't need the details, they'll
| figure it out. You give them a scrap and they'll get the
| rest. It's a different way of thinking that we're not
| used to, and don't understand unless we're exposed to it
| first-hand, e.g. through red-teaming.
|
| For example, another way to think of this is that you
| have a buffer of initialized memory, containing a view
| onto some piece of data, from which you serve a subset to
| the user, but you get the format of the subset wrong, so
| that parts of the view leak through. That's a bleed.
|
| Depending on the context, the bleed may be enough or it
| might be less severe, but the slightest semantic gap can
| be chained and built up into something major. Even if it
| takes 6 chained hoops to jump through, that's a low bar
| for a determined attacker.
| woodruffw wrote:
| > For example, another way to think of this is that you
| have a buffer of initialized memory (no unsafe),
| containing a view onto some piece of data, from which you
| serve a subset to the user, but you get the format of the
| subset wrong, so that parts of the view leak through.
| That's a bleed.
|
| If there's full initialization then this is just a logic
| error, no? Apart from some kind of capability typing over
| ranges of bytes (not very ergonomic), this would be a
| very difficult subtype of "bleed" to statically describe,
| much less prevent.
| jorangreef wrote:
| Yes, exactly. That's what I was driving at. It's just a
| logic error, that leaks sensitive information, by virtue
| of leaking the wrong information. File formats in
| particular can make this difficult to get right. For
| example, the ZIP file format (that I have at least some
| experience with bleeds in) has at least 9 different
| places where a bleed might happen, and this can depend on
| things like: whether files are added incrementally to the
| archive, the type of string encoding used for file names
| in the archive etc.
| woodruffw wrote:
| Makes sense! My colleagues work on some research[1]
| that's intended to be the counterpart to this:
| identifying which subset of a format parser is actually
| activated by a corpus of inputs, and automatically
| generating a subset parser that only accepts those
| inputs.
|
| I think you mentioned WUFFS before the edit; I find that
| approach very promising!
|
| [1]: https://www.darpa.mil/program/safe-documents
| jorangreef wrote:
| Thanks! Yes, I did mention WUFFS before the edit, but
| then figured I could make it a bit more detailed. WUFFS
| is great.
|
| The SafeDocs program and approach looks incredible.
| Installing tools like this at border gateways for SMTP
| servers, or as a front line defense before vulnerable AV
| engine parsers (as Pure is intended to be used), could
| make such a massive dent against malware and zero days.
| raphlinus wrote:
| Thanks for the explanation. I would consider that type of
| logic error more or less impossible to defend at the
| language level, but I can see how analysis tools can be
| helpful.
| tptacek wrote:
| You have my attention!
| jorangreef wrote:
| Wow, that's saying something!
|
| The tool is called Pure [1]. It was originally written in
| JavaScript and open-sourced, then rewritten for Microsoft
| in C at their request for performance (running sandboxed)
| after it also detected David Fifield's "A Better Zip
| Bomb" as a zero day.
|
| I'd love to rewrite it in Zig to benefit from the checked
| arithmetic, explicit control flow and spatial safety--
| there are no temporal issues for this domain since it's
| all run-to-completion single-threaded.
|
| Got to admit I'm a little embarrassed it's still in C!
|
| [1] https://github.com/ronomon/pure
| dkersten wrote:
| I'm not sure I understand the value of an allocator that doesn't
| reuse allocations, as a bug prevention thing. Is it just for
| performance? (Since its never reused, allocation can simply be
| incrementing an offset by the size of the allocation)? Because
| beyond that, you can get the same benefit in C by simply never
| calling free on the memory you want to "protect" against use-
| after-free.
| anonymoushn wrote:
| The allocations are freed and the addresses are never reused.
| So heap use-after-frees are segfaults.
| kaba0 wrote:
| I believe it is only for performance, as malloc will have to
| find place for the allocation, while it is a pointer bump only
| for a certain kind of allocator.
| AndyKelley wrote:
| I have one trick up my sleeve for memory safety of locals. I'm
| looking forward to experimenting with it during an upcoming
| release cycle of Zig. However, this release cycle (0.10.0) is all
| about polishing the self-hosted compiler and shipping it. I'll be
| sure to make a blog post about it exploring the tradeoffs - it
| won't be a silver bullet - and I'm sure it will be a lively
| discussion. The idea is (1) escape analysis and (2) in safe
| builds, secretly heap-allocate possibly-escaped locals with a
| hardened allocator and then free the locals at the end of their
| declared scope.
| skullt wrote:
| Does that not contradict the Zig principle of no hidden
| allocations?
| kristoff_it wrote:
| I don't know the precise details of what Andrew has in mind
| but the compiler can know how much memory is required for
| this kind of operation at compile time. This is different
| from normal heap allocation where you only know how much
| memory is needed at the last minute.
|
| At least in simple cases, this means that the memory for
| escaped variables could be allocated all at once at the
| beginning of the program not too differently to how the
| program allocates memory for the stack.
| messe wrote:
| Static allocation at the beginning of the program like that
| can only work for single threaded programs with non-
| recursive functions though, right?
|
| I'd hazard a guess that the implementation will rely on
| use-after-free faulting, meaning that the use of any
| escaped variable will fault rather than corrupting the
| stack.
| remexre wrote:
| Could this be integrated into the LLVM SafeStack pass? (I don't
| know how related Zig still is to LLVM, or if your thing would
| be implemented there.)
| LAC-Tech wrote:
| Safe enough. You can use `std.testing.allocator` and it will
| report leaks etc in your test cases.
|
| What rust does sounds like a good idea in theory. In practice it
| rejects too many valid programs, over-complicates the language,
| and makes me feel like a circus animal being trained to jump
| through hoops. Zigs solution is hands down better for actually
| getting work done, plus it's so dead simple to use arena
| allocation and fixed buffers that you're likely allocating a lot
| less in the first place.
|
| Rust tries to make allocation implicit, leaving you confused when
| it detects an error. Zig makes memory management explicit but
| gives you amazing tools to deal with it - I have a much clearer
| mental model in my head of what goes on.
|
| Full disclaimer, I'm pretty bad at systems programming. Zig is
| the only one I've used where I didn't feel like memory management
| was a massive headache.
| afdbcreid wrote:
| Do compilers really can never call `free()`?
|
| Simple compiler probably can. Most complex probably cannot (I
| don't want to imagine a Rust compiler without freeing memory: it
| has 7 layers of lowering (source
| code->tokens->ast->HIR->THIR->MIR->monomorphized MIR, excluding
| the final LLVM IR) and also allocates a lot while type-checking
| or borrow-checking).
|
| What is most interesting to me is the average compiler. Does
| somebody have statistics on the average amount compilers allocate
| and free?
| com2kid wrote:
| > Do compilers really can never call `free()`?
|
| I worked on, one of the many, Microsoft compiler teams, though
| as a software engineer in test not directly on the compiler
| itself, and I believe the lead dev told me they don't free any
| memory, though I could be misremembering since it was my first
| job out of college.
|
| Remember C compilers are often one file at a time (and a LOLWTF
| # of includes), and the majority of work goes into making a
| single output file, and then you are done. Freeing memory would
| just take time, better to just hand it all back to the OS.
|
| Also compilers are obsessed with correctness, generating
| incorrect code is to be avoided at all costs. Dealing with
| memory management is just one more place where things can go
| wrong. So why bother?
|
| I do remember running out of memory using link time code gen
| though, back when everything was 32bit.
|
| Related, I miss the insane dedication to quality that team had.
| Every single bug had a regression test created for it. We had
| regression tests 10-15 years old that would find a bug that
| would have otherwise slipped through. It was a great way to
| start my career off, just sad I haven't seen testing done at
| that level since then!
| kaba0 wrote:
| Bootstrapping aside, a compiler written in a GCd language
| would make perfect sense. It really doesn't have any reason
| to go lower level than that (other than of course, if one
| wants to bootstrap it in the same language that happens to be
| a low-level one)
| com2kid wrote:
| There is no reason to free memory. Your process is going to
| hard exit after a set workload.
|
| If you wrote a compiler in a GCd language, you'd want to
| disable the collector because that just takes time, and
| compilers are slow enough as it is!
| kaba0 wrote:
| A good GC will not really increase the execution time at
| all -- they turn on only after a significant "headroom"
| of allocations. For short runs they will hardly do any
| work.
|
| Also, most of the work will be done in parallel, and I
| really wouldn't put aside that a generational GC's
| improved cache effect (moving still used objects close)
| might even improve performance (all other things being
| equal, but they are never of course). All in all, do not
| assume that just because a runtime has a GC it will
| necessarily be slower, that's a myth.
| IshKebab wrote:
| I presume he means that compilers _could_ be written to never
| call `free()`. I 'm sure that most of them are not written like
| that, though they do tend to be very leaky and just `exit()` at
| the end rather than clean everything up neatly (partly because
| it's faster).
| woodruffw wrote:
| LLVM uses a mixed strategy: there's both RAII _and_ lots of
| globally allocated context that only gets destroyed at program
| cleanup. I believe GCC is the same.
|
| Rustc is written entirely in Rust, so I would assume that it
| doesn't do that.
| notriddle wrote:
| The headline feature of rustc memory management is the use of
| arenas: https://github.com/rust-
| lang/rust/blob/10f4ce324baf7cfb7ce2b... //!
| The arena, a fast but limited type of allocator. //!
| //! Arenas are a type of allocator that destroy the objects
| within, all at //! once, once the arena itself is
| destroyed. They do not support deallocation //! of
| individual objects while the arena itself is still alive. The
| benefit //! of an arena is very fast allocation; just
| a pointer bump.
|
| The other thing (not specifically mentioned in this comment,
| but mentioned elsewhere, and important to understanding why
| it work the way it does) is that if everything in the arena
| gets freed at once, it implies that you can soundly treat
| everything in the arena as having exactly the same lifetime.
|
| You can see an example of how every ty::Ty<'tcx> in rustc
| winds up with the same lifetime, and an entry point for
| understanding it more, here in the dev guide: https://rustc-
| dev-guide.rust-lang.org/memory.html
|
| However, arena allocation doesn't cover all of the dynamic
| allocation in rustc. Rustc uses a mixed strategy: there's
| both RAII and lots of arena allocated context that only gets
| destroyed at the end of a particular phase.
| woodruffw wrote:
| Yep -- arenas compose very nicely with lifetimes, and
| basically accomplish the same thing as global allocation
| (in effect, a 'static arena) but with more control.
| zRedShift wrote:
| For further reading, I recommend Niko's latest blog,
| tangentially related to rustc internals (and arena
| allocation): https://smallcultfollowing.com/babysteps/blog/
| 2022/06/15/wha...
| TazeTSchnitzel wrote:
| > Do compilers really can never call `free()`?
|
| If a compiler has to be run multiple times in the same process,
| it may use an area allocator to track all memory, so you can
| free it all in one go once you're done with compilation.
| Delaying all freeing until the end effectively eliminates
| temporal memory issues.
| MaxBarraclough wrote:
| I imagine plenty of compilers do call _free_ , but here's a
| 2013 article by Walter Bright on modifying the dmd compiler to
| never free, and to use a simple pointer-bump allocator (rather
| than a proper malloc) resulting in a tremendous improvement in
| performance. [0] (I can't speak to how many layers dmd has, or
| had at the time.)
|
| The never-free pattern isn't just for compilers of course, it's
| also been used in missile-guidance code.
|
| [0]
| https://web.archive.org/web/20190126213344/https://www.drdob...
| verdagon wrote:
| A lot of embedded devices and safety critical software sometimes
| don't even use a heap, and instead use pre-allocated chunks of
| memory whose size is calculated beforehand. It's memory safe, and
| has much more deterministic execution time.
|
| This is also a popular approach in games, especially ones with
| entity-component-system architectures.
|
| I'm excited about Zig for these use cases especially, it can be a
| much easier approach with much less complexity than using a
| borrow checker.
| jorangreef wrote:
| This is almost what we do for TigerBeetle, a new distributed
| database being written in Zig. All memory is statically
| allocated at startup [1]. Thereafter, there are zero calls to
| malloc() or free(). We run a single-threaded control plane for
| a simple concurrency model, and because we use io_uring--
| multithreaded I/O is less of a necessary evil than it used to
| be.
|
| I find that the design is more memory efficient because of
| these constraints, for example, our new storage engine can
| address 100 TiB of storage using only 1 GiB of RAM. Latency is
| predictable and gloriously smooth, and the system overall is
| much simpler and fun to program.
|
| [1] "Let's Remix Distributed Database Design"
| https://www.youtube.com/channel/UC3TlyQ3h6lC_jSWust2leGg
| infamouscow wrote:
| > Latency is predictable and gloriously smooth, and the
| system overall is much simpler and fun to program.
|
| This has also been my experience building a database in Zig.
| It's such a joy.
| snicker7 wrote:
| How exactly is pre-allocation safer? If you would ever like to
| re-use chunks of memory, then wouldn't you still encounter
| "use-after-free" bugs?
| nine_k wrote:
| No; every chunk is for single, pre-determined use.
|
| Imagine all variables in your program declared as static.
| This includes all buffers (with indexes instead of pointers),
| all nested structures, etc.
| bsder wrote:
| Normally you do this on embedded so that you know _exactly_
| what your memory consumption is. You never have to worry
| about Out of Memory and you never have to worry about Use
| After Free since there is no free. That memory is yours for
| eternity and what you do with it is up to you.
|
| It doesn't, however, prevent you from accidentally scribbling
| over your own memory (buffer overflow, for example) or from
| scribbling over someone else's memory.
| verdagon wrote:
| The approach can reuse old elements for new instances of the
| same type, so to speak. Since the types are the same, any
| use-after-free becomes a plain ol' logic error. We use this
| approach in Rust a lot, with Vecs.
| olig15 wrote:
| But if you have a structure that contains offsets into
| another buffer somewhere, or an index, whatever - the wrong
| value here could be just as bad as a use-after-free. I
| don't see how this is any safer. If you use memory after
| free from a malloc, with any chance you'll hit a page
| fault, and your app will crash. If you have a index/pointer
| to another structure, you could still end up reading past
| the end of that structure into the unknown.
| verdagon wrote:
| That's just a logic error, and not memory unsafety which
| might risk UB or vulnerabilities. The type system
| enforces that if we use-after-"free" (remember, we're not
| free'ing or malloc'ing), we just get a different instance
| of the same type, which is memory-safe.
|
| You do bring up a valid broader concern. Ironically, this
| is a reason that GC'd systems can sometimes be better for
| privacy than Ada or Rust which uses a lot more
| Vec+indexes. An index into a Vec<UserAccount> is riskier
| than a Java List<UserAccount>; a Java reference can never
| suddenly point to another user account like an index
| could.
|
| But that aside, we're talking about memory safety, array-
| centric approaches in Zig and Rust can be appropriate for
| a lot of use cases.
| pjmlp wrote:
| In high integrity computing that is pretty much safety
| related, if that logic error causes someone to die due to
| corrupt data, like using the wrong radiation value.
| [deleted]
| tialaramex wrote:
| But, Java has exactly the same behaviour, the typical
| List in Java is ArrayList which sure enough has an
| indexed get() method.
|
| There seems to be no practical difference here. Rust can
| do a reference to UserAccount, and Java can do an index
| into an ArrayList of UserAccounts. Or vice versa. As you
| wish.
| kaba0 wrote:
| These are 1000 times worse than even a segfault. These are
| the bugs you won't notice until they crop up at a wildly
| different place, and you will have a very hard time
| tracking it back to their origin (slightly easier in Rust,
| as you only have to revalidate the unsafe parts, but it
| will still suck)
| pcwalton wrote:
| Even in this environment, you can still have dangling pointers
| to freed stack frames. There's no way around having a proper
| lifetime system, or a GC, if you want memory safety.
| infamouscow wrote:
| > Even in this environment, you can still have dangling
| pointers to freed stack frames.
|
| How frequently does this happen in real software? I learned
| not to return pointers to stack allocated variables when I
| was 12 years old.
|
| > There's no way around having a proper lifetime system, or a
| GC, if you want memory safety.
|
| If you're building an HTTP caching program where you know the
| expiration times of objects, a Rust-style borrow-checker or
| garbage collector is not helping anyone.
| Arnavion wrote:
| >I learned not to return pointers to stack allocated
| variables when I was 12 years old.
|
| So, if you slip while walking today, does that mean you
| didn't learn to walk when you were one year old?
| im3w1l wrote:
| Well if get rid of not just the heap, but the stack too...
| turn all variables into global ones, then it will be safe.
|
| This means we lose thread safety and functions become non-
| reentrant (but easy to prove safe - make sure graph of
| A-calls-B is a acyclical).
| verdagon wrote:
| Yep, or generational references [0] which also protect
| against that kind of thing ;)
|
| The array-centric approach is indeed more applicable at the
| high levels of the program.
|
| Sometimes I wonder if a language could use an array-centric
| approach at the high levels, and then an arena-based approach
| for all temporary memory. Elucent experimented with something
| like this for Basil once [1] which was fascinating.
|
| [0] https://verdagon.dev/blog/generational-references
|
| [1] https://degaz.io/blog/632020/post.html
| com2kid wrote:
| > Yep, or generational references [0] which also protect
| against that kind of thing ;)
|
| First off, thank you for posting all your great articles on
| Vale!
|
| Second off, I just read the generational references blog
| post for the 3rd time and now it makes complete sense, like
| stupid obvious why did I have problems understanding this
| before sense. (PS: The link to the benchmarks is dead :( )
|
| I hope some of the novel ideas in Vale make it out to the
| programming language world at large!
| verdagon wrote:
| Thank you! I just fixed the link, thanks for letting me
| know! And if any of my articles are ever confusing, feel
| welcome to swing by the discord or file an issue =)
|
| I'm pretty excited about all the memory safety advances
| languages have made in the last few years. Zig is doing
| some really interesting things (see Andrew's thread
| above), D's new static analysis for zero-cost memory
| safety hit the front page yesterday, we're currently
| prototyping Vale's region borrow checker, and it feels
| like the space is really exploding. Good time to be
| alive!
| brundolf wrote:
| Rust's borrow checker would be much calmer in these scenarios
| too, wouldn't it? If there are no lifetimes, there are no
| lifetime errors
| thecatster wrote:
| Rust is definitely different (and calmer imho) on bare metal.
| That said (as much of a Rust fanboy I am), I also enjoy Zig.
| the__alchemist wrote:
| Yep! We've entered a grey areas, where some Rust embedded
| libs are expanding the definitions of memory safety, and what
| the borrow checker should evaluate beyond what you might
| guess. Eg, structs that represent peripherals, that are now
| checked for ownership; the intent being to prevent race
| conditions. And Traits being used to enforce pin
| configuration.
| ajross wrote:
| > In practice, it doesn't seem that any level of testing is
| sufficient to prevent vulnerabilities due to memory safety in
| large programs. So I'm not covering tools like AddressSanitizer
| that are intended for testing and are not recommended for
| production use.
|
| I closed the window right there. Digs like this (the "not
| recommended" bit is a link to a now famous bomb thrown by
| Szabolcs on the oss-sec list, not to any kind of industry
| consensus piece) tell me that the author is grinding an axe and
| not taking the subject seriously.
|
| Security is a spectrum. There are no silver bullets. It's OK to
| say something like "Rust is better than Zig+ASan because", it's
| quite another to refuse to even treat the comparison and pretend
| that hardening tools don't exist.
|
| This is fundamentally a strawman, basically. The author wants to
| argue against a crippled toolchain that is easier to beat instead
| of one that gets used in practice.
| klyrs wrote:
| As a Zig fan, I disagree. I think it's really important to
| examine the toolchain that beginners are going to use.
|
| > I'm also focusing on software as it is typically shipped,
| ignoring eg bounds checking compilers like tcc or quarantining
| allocators like hardened_malloc which are rarely used because
| of the performance overhead.
|
| To advertize that Zig is perfectly safe because things like
| ASan exist would be misleading, because that's not what users
| get out of the box. Zig is up-front and honest about the
| tradeoffs between safety and performance, and this evaluation
| of Zig doesn't give any surprises if you're familiar with how
| Zig describes itself.
| ajross wrote:
| > To advertize that Zig is perfectly safe because things like
| ASan exist would be misleading
|
| Exactly! And for the same reason. You frame your comparison
| within the bounds of techniques that are used in practice.
| You don't refuse to compare a tool ahead of time,
| _especially_ when doing so reinforces your priors.
|
| To be blunt: ASan is great. ASan finds bugs. Everyone should
| use ASan. Everyone should advocate for ASan. But doing that
| cuts against the point the author is making (which is
| basically the same maximalist Rust screed we've all heard
| again and again), so... he skipped it. That's not good faith
| comparison, it's spin.
| KerrAvon wrote:
| ASAN doesn't add memory safety to the base language. It
| catches problems during testing, assuming those problems
| occur during the testing run (they don't always! ASAN is
| not a panacea!). It's perfectly fair to rule it out of
| bounds for this sort of comparison.
| anonymoushn wrote:
| I would like Zig to do more to protect users from dangling stack
| pointers somehow. I am almost entirely done writing such bugs,
| but I catch them in code review frequently, and I recently moved
| these lines out of main() into some subroutine:
| var fba = std.heap.FixedBufferAllocator.init(slice_for_fba);
| gpa = fba.allocator();
|
| slice_for_fba is a heap-allocated byte slice. gpa is a global.
| fba was local to main(), which coincidentally made it live as
| long as gpa, but then it was local to some setup subroutine
| called by main(). gpa contains an internal pointer to fba, so you
| run into trouble pretty quickly when you try allocating memory
| using a pointer to whatever is on that part of the stack later,
| instead of your FixedBufferAllocator.
|
| Many of the dangling stack pointers I've caught in code review
| don't really look like the above. Instead, they're dangling
| pointers that are intended to be internal pointers, so they would
| be avoided if we had non-movable/non-copyable types. I'm not sure
| such types are worth the trouble otherwise though. Personally,
| I've just stopped making structs that use internal pointers. In a
| typical case, instead of having an internal array and a slice
| into the array, a struct can have an internal heap-allocated
| slice and another slice into that slice. like I said, I'd like
| these thorns to be less thorny somehow.
| alphazino wrote:
| > so they would be avoided if we had non-movable/non-copyable
| types.
|
| There is a proposal for this that was accepted a while ago[0].
| However, the devs have been focused on the self-hosted compiler
| recently, so they're behind on actually implementing accepted
| proposals.
|
| [0] https://github.com/ziglang/zig/issues/7769
| 10000truths wrote:
| Alternatively, use offset values instead of internal pointers.
| Now your structs are trivially relocatable, and you can use
| smaller integer types instead of pointers, which allows you to
| more easily catch overflow errors.
| anonymoushn wrote:
| This is a good idea, but native support for slices tempts one
| to stray from the path.
| throwawaymaths wrote:
| This. I believe it is in the works, but postponed to finish up
| self-hosted.
|
| https://github.com/ziglang/zig/issues/2301
| belter wrote:
| 1 year ago, 274 comments.
|
| "How Safe Is Zig?": https://news.ycombinator.com/item?id=26537693
___________________________________________________________________
(page generated 2022-06-23 23:00 UTC)