I noticed he mentioned that his game was running at 50hz - that's true if an Amiga is running on PAL, but if it's a US machine it'll be 60hz when running with NTSC.
Back in the day, most Amiga games were made in Europe, so were tuned for 50Hz refresh, and played 'normally' over here. At 60hz, they would feel wrong, and likewise, US games could feel sluggish and unsatisfying.
Locking the machine to the monitor refresh rate in this way was a curse but also a blessing. It allowed the machines to drive a standard television in various regions, although the image was somewhat blurry, but also the use of dedicated monitors once you started taking it seriously. This reduced the price point and allowed lots of kids to convince their parents to get one :) The higher resolution modes just didn't work out though, so lots of stuff targeted the 320x256 resolution.
The surprise benefit was also that the amiga could genlock to a TV signal and used to overlay onto a video feed, leading to many interesting cheap graphics overlay capabilities which were otherwise totally unobtainable by small TV channels or enthusiasts.
'the only language where 90% of the world's memory safety vulnerabilities have occurred in the last 50 years'
Let's assume this is true, and ignoring the fact that it's not possible for more than 1 language to have >50% of the vulnerabilities, we are left pondering what proportion of the worlds software is written in C. If it's >90%, then C is safer than average :)
Assuming that C is the most successful language of its generation, among those letting purposely do anything with memory access, well, it's unfortunately possible to have it do anything. It's like saying with surprise that a harpoon gun may even kill people.
Then successively implemented languages may have improved things (or they would have been useless efforts). When a single one of them will reach half the popularity of C (without running in any "unsafe" mode) it will be another great success.
The original article was clearly just a polemical overkill by a maniac of another language. Or by a fisherman who kills fishes slapping them.
All information is ephemeral, but I don't honestly think that argument holds much weight here.
I'm currently listening to a record which was pressed before I was born, and that will outlast me. My CDs were ripped around 2000 to a drive and i've streamed then since. I've still got the CDs though, and the last time I played one it worked fine on my 1989 vintage transport.
That has gone back and forth numerous times over the decades, particularly in legal cases concerning unlicensed content sharing. I think the consensus ATM is that they generally aren't on their own, largely because most network access is through shared NAT arrangements.
At most an IP address (definitely v4, v6 depending on your arrangement) identifies a household or office, not an individual, and “it seems someone hacked the wireless, or one of my smart devices, or a rouge plugin turned me into a residential proxy, etc.” muddies the water further, often an IP address identifies nothing more than which mobile data provider or VPN provider the user was connected through.
As a simple for instance: No one warns when all that is collected is the calling hosts' apparent IP address in their web server or other service logs. Only once entries with record of the address are explicitly linked to other PII (i.e. if URLs contain PII like names, addresses, etc, so those are logged alongside the calling address) is it an issue - and even then the recording of that information in the wrong places is the problem (in the HTTP logs example, what is that data about the user even doing existing in URIs?) not the calling IP address.
You are somewhat confusing two distinct concepts. IP addresses are considered to be personal data because they can be linked to single individual and controller is allowed to give this personal data to someone who can do the linking (e.g. police who can then request logs from ISP or NAT connection logs from the company).
Now it doesn't mean it will always link to single individual, but unless controller can be sure that there are always at least 2 people behind the IP and the devices on that side do not keep enough information to ever link IP+timestamp+destination service to single individual, the controller essentially must assume that IP address is personal data.
This is different from civil liabilities. National courts determine what is the threshold for that. For example in Finland the court has ruled that if the owner of the car cannot name the person who parked the then the presumption is that they did it and are responsible for parking contract breach (KKO 2026:24). National courts could end up with similar ruling for civil liability for sharing content, i.e. assumption that the IP owner either is the person who shared it or knows who they did it & if they refuse to name the person then presumption is that they did it.
Yeah, there were two busses, and the bigger ones were certainly rather impressive. I built a PDP-11/23 out of spare bits gifted from a university physics department in the late 80s, and it was awesome, but nothing like an 11/70 or anything like that!
Is it only me that would have expected curl_getenv() to have an assert that it's argument isn't NULL?
I know this doesn't stop runtime problems in release builds, but i'd have thought this sort of simple precondition check would help users find problems in their library useage.
It's not going to stop you passing a non-terminated string, or other such invalid input though, which is I guess more the point, that it's totally possible in C to produce good looking but actually invalid arguments that can't be spotted at runtime without UB (out of bounds access etc).
Edit: Actually thinking about this more, I guess the problem is that you are likely linking against a release library implementation, so it's not possible to add a precondition without introducing a runtime overhead, which is probably more likely what we are talking about with this case.
If I were doing a code review, I would probably accept the code either with or without the assertion. The context of curl_getenv() makes it clear that null is not acceptable. If the author of curl_getenv() had evidence that callers are frequently breaking the contract by passing null, then perhaps the assertion would help shed some light on violators. Otherwise, I would expect everyone to play by the rules, making the assertion unnecessary.
The problem with asserts is that they are pretty dramatic and you crash the entire program.
We generally did this in the avahi libraries, be fairly liberal with asserts that "shouldn't happen", it is a source of complaints though because basically you can be using a third party library that uses avahi and have your program crash due to a bug in that library, or in avahi. It's extra fun when using some historical libc systems such as "NSS" and you load a plugin to do hostname resolution, which nss-mdns does.. now you can have any program on the entire system crash if you are assert happy.
On the one hand I agree that if the result is going to be memory un-safety then perhaps you should assert, but more ideally you'd just fail gracefully and throw or return an error. That can sometimes be tricky though, if there is no good way to return an error or return a NULL value or similar. Depending on the API.
But in the case of curl_getenv, returning NULL seems a valid possibility (https://curl.se/libcurl/c/curl_getenv.html) as that is indicated to be done if you don't find the requested environment variable. Arguably the NULL environment variable is not found. so, this feels likely to be acceptable. Though I could see an argument for you now assuming the environment variable you were actually looking for not existing, but you didn't actually ask for one, and now your logic is broken and maybe you introduce a different class of security bug because you change your behaviour based on some environment variable not existing.
Returning to the context of this post, this is one of the things I really like about rust. (And zig, haskell, typescript, swift and others). These languages make invalid states impossible to represent. If my function takes a value of type T (or &T), you can't accidentally receive NULL. So you just don't need to worry about this stuff any more. The compiler simply won't compile the program if type checking fails. At runtime, I only have to consider valid values.
Crashing a program is always a much better alternative than behaviours that silently lead to memory corrupt, having much severe outcomes than a crash.
Ah but what high integrity computing, well there neither crashes nor memory corruption are welcomed, hence programming guidelines and certification workflows that would make most C devs cry with the language features they are allowed to use, and how each line of code gets analysed by tools and humans.
Yes, but null pointers are so pervasive in C code that we really can't afford to put assertions everywhere. It's often better to let the app crash on violations.
A bug is a bug even when it doesn't clearly manifest itself 100% of the time, and furthermore it is pretty much guaranteed that NULL dereference crashes with segfault in practice, only not for the people playing theoretic games whose essence of life is finding gotchas where it maybe isn't so and then feeling smarter than everyone else.
But it's >> 99.9% true that this will just crash even though it's acshually UB, nasal demons and so forth. Now raise this << 0.1% likelihood that it isn't true on some system with some compiler and build flags, to the power of the number of distinct deployed configurations out there, and you get the result which is the correct engineering decision of just moving on instead of spending your life filling straightforward code with pointless boilerplate assertions.
NB it can make sense to assert nonnull when the condition won't be tested on all code paths or the intention is otherwise not super obvious.
> it's >> 99.9% true that this will just crash even though it's acshually UB, nasal demons and so forth.
Is it though? Linux saw enough bugs from that kind of issue that they now build with -fno-delete-null-pointer-checks and accept the (supposed) performance penalty.
The kernel is perhaps bit special. In the past they had bugs such as first derferencing and then checking for null and weird possibilities to map the zero page. But today I am not convinced this is really needed.
In general on a system where you trap when accessing the zero page, this optimization should be safe and a null pointer dereferences should (safely) trap.
> In general on a system where you trap when accessing the zero page, this optimization should be safe and a null pointer dereferences should (safely) trap.
If you mean that C compiler writers "should" prioritise sanity over high scores on microbenchmarks, then I agree. However in practice they do not and this optimization is not remotely safe.
I don't understand your comment - dereferencing a null pointer is unsafe, in the sense that it does not reliably crash but may do other things, as we saw in the kernel case we're talking about. Yes that particular case was only exploitable if you mapped the zero page, but given how all-bets-are-off a situation it created (where extremely experienced programmers thought they knew what the code did, thought it was safe, and were wrong), I would not want to count on all cases not being exploitable without mapping the zero page.
We are talking about an extremely simple straightforward API with an obvious contract. It's good enough for this function to reliably surface almost all wrong uses with a segfault immediately. Wrong use will result in segfaults and otherwise bugs and crashes. The goal is not to work when used wrong but to work when used right. You cannot save the world from scratch in every little function. You still have a job to get done, and you have to move on.
> You cannot save the world from scratch in every little function. You still have a job to get done, and you have to move on.
Or you can take all of 10 minutes to put sanity-check assertions at the start of all your public-facing API functions, eliminating a source of security bugs, get on with your life, and worry about the performance implications as and when it becomes a problem (hint: it's never going to become a problem).
You can try and do this if it's a relatively narrow public facing API, but otherwise this is a theoretic ideal. In practice, if you add an assertion for every pointer argument to every little function, you'll go insane, and it is completely pointless, and the code will not be readable anymore.
There are so many other interesting and relevant invariants that are usually in an API contract that are much harder or impossible to check upfront (let alone express formally in a type system), and even violations may be impossible to diagnose when they happen.
People focus on NULL because that's the only way they can apply their silly limited type systems. But NULL checks give very little return for investment. In practice, you'll see templated Option<T> types and whatnot, and when I have to look at or even work with such code I want to kill myself because it's so painful.
No, people focus on a handful of things like null, buffer overrun, and use-after-free because they still make up the majority of security vulnerabilities that we see exploited in the wild. You may imagine that subtle logic errors are more common, but the data doesn't bear that out; also FWIW I've never seen one of these detailed invariants be impossible to express in a type system if you spend 5 minutes actually trying.
Given a, b, c input parameters to my func, it must hold that that a->m->t == b->t. c->mutex must be held, and c->cond is the condition variable that goes with c->mutex and will release any waiters on the buffer contained in a.
Or: Integer x is representable using 12 bits only, Integer y should be a multiple of N and I have a integer s is used as a bit-shift that should be less than 8.
Or: I need to guarantee that no locks have to be taken and no allocations have to be made on this complicated looking codepath. While holding a lock, we must not do any syscalls (syscall a, b, c are ok though), and surely not make any logging calls.
I know only one system that can express this, it's called STRAIGHTFORWARD CODE, and it requires doing engineering and casual logic out-of-band, and yes it does include making mistakes and repairing them incrementally.
I don't know a type system that would let me explain these things to me and tell me where I was wrong. But maybe you can show me, with 5 minutes of actually trying?
So define a wrapper type that represents that invariant (it's not going to take up space at runtime), where the only constructor enforces it?
> Integer x is representable using 12 bits only, Integer y should be a multiple of N and I have a integer s is used as a bit-shift that should be less than 8.
Those are all standard things that already exist?
> Or: I need to guarantee that no locks have to be taken and no allocations have to be made on this complicated looking codepath. While holding a lock, we must not do any syscalls (syscall a, b, c are ok though), and surely not make any logging calls.
Sounds like a pretty standard free monad case? Define a command algebra in which the "ok" syscalls are a subtype, and then require that the thing you want to only use the ok calls to have a type that reflects that?
Please, go ahead and type the example. I think you are trolling.
> Define a command algebra in which the "ok" syscalls are a subtype
Dude, it's clear you're not doing any actual work. You are living in an ivory tower, and you underestimate the complexity and detail and volatility of real world applications by at least 3 orders of magnitude. You don't understand how to modularize and contain complexity.
You _cannot_ complete a project with this attitude.
You are ignorant of the fact that a type system is necessarily a blunt simplification of the real complexity. Therefore, use of types must be pragmatic, and actual logic must be coded in normal code (which should be obvious but it isn't to type theory weirdos). Otherwise, you require dependent typing or whatever, and you will have to write your code twice, once in a usable programming language and once in a very unusable programming language. Much more than only twice actually, given that all the implicit detail should apprently go explicitly formalized at the type level.
Just to make sure I'm not entirely talking out of my arse because I'm so incredibly annoyed by your otherworldly proposition, I asked an AI about the sel4 microkernel. It consists of 10,000 lines of C code (that says a lot about its practical utility, which is very limited), and of 1,3 million lines of manually written proof code (which says a lot about the practicality of proving).
It takes a lot longer to figure out if it'll be a problem than to just add the check. And you don't have to ponder whether it's possible for a null to get there, because now it's fine if it does.
Are you talking about extending the API contract to allow for NULL? That is often the path to madness, especially if it requires complicating the signature (return value etc). Better to just assert/crash.
Because no one is expecting it to work if a null is passed. Your total range of behaviours left are crashes, doesn't crash and is silently ok, or doesn't crash and causes something worse (data corruption, you get your product in a CVE, that area).
My proposition is that "it's silently ok" isn't likely enough, which is in line with your position on "don't extend the contract to accept null". So what's left is crash, or something worse.
So if those are your choices, don't waste time justifying that a null can't get there, just add a check to ensure you get the better behaviour. It takes seconds.
If you follow that line of reasoning, you will end up testing almost every pointer before accessing it. The reason is that you are extending your valid state space massively since you aren't able to specify "this subset of 7 trillion distinct states is invalid, if it was the case we would have failed before".
You are requiring yourself to find a valid outcome for an input that doesn't make _any_ sense in the context of what your application is meant to achieve. How is that not a Sysiphean task?
You're not "extending" the valid state space. That null value being passed to that function is already a potential state of your program.
You're actually pruning the valid state space; before, when the null value is passed to the function, there are more operations performed that have uncertain consequences. If you assert-and-fail when you get the null input, you've pruned those states.
So if I understand correctly now, you _do_ proclaim to put asserts, not write code that somehow copes with the "possiblity" of NULL.
"Because no one is expecting it to work if a null is passed", so you can do whatever. If you write an assert for every pointer passed to every function, that will be a lot of asserts, for pretty much the same outcome in practice. Asserts are just marginally more ergonomic when they trigger, but are a nuisance in the code often. So my position is to use them judiciously, but not overdo it, be instead focused on the actual task.
When the lack of non-null assertions is an actual problem during development, you have much larger structural issues.
An assert is not guaranteed to terminate the process. In C, the most common implementation choice is to completely omit the check if you're not building in debug mode.
> it's not possible to add a precondition without introducing a runtime overhead
Indeed. Adding an assertion to a single function isn't a big deal, but if every function has to check all of it's arguments, that's going to add up. And even if you could have the assertion only in debug builds, that isn't enough unless you have a very exhaustive test suite, because an edge case could trigger undefined behavior in production in a way that wasn't exercised during testing.
In fact, the fact that the rust compiler adds runtime checks for array indexes if it can't prove the index is in bounds is a criticism some c programmers have of rust.
> In fact, the fact that the rust compiler adds runtime checks for array indexes if it can't prove the index is in bounds is a criticism some c programmers have of rust.
And the fact that after a half a century we're still debating how much we really need to care about U stuff like this when we get severe bugs in a major piece of software written in C seemingly every week is a criticism that pretty much all Rust programmers have of C.
Considering the amount of C programs that exist, the "we see severe bugs in C code seemingly every week" is on the same level of propaganda as we see "crime in the news every week" when the real societal problems are entirely different.
The difference is that while we don't have a viable model for zero crime societies, we do have languages that don't suffer from nearly as many memory safety bugs
It is so bad as C culture, that the only way to fix the culture is by having hardware where those C programmers no longer have a say on bounds checking.
Most systems languages, with exception of C, have ways to do bounds checking, even C++ and Objective-C, by using the respective collection classes.
I think the history of this is that these operations were common with assembly programmers, so when C came along, these were included in the language to allow these developers to feel they weren't leaving lots of performance behind.
Look at the addressing modes for the PDP-11 in https://en.wikipedia.org/wiki/PDP-11_architecture and you'll see you can write (R0)+ to read the contents of the location pointed to by R0, and then increment R0 afterwards (so a post increment).
Back in the day, compilers were simple and optimisations weren't that common, so folding two statements into one and working out that there were no dependencies would have been tough with single pass compilers.
You could argue that without such instructions, C wouldn't have been embraced quite so enthusiastically for systems programming, and the world would have looked rather different.
Additionally, those indirect memory instructions ended up disappearing because it complicated virtual memory implementations. It was a pain in the ass to describe the multiple places in memory an instruction could be accessing and which actually faulted to a fault handler, not to mention having to roll back all that state on more complex designs.
I worked on a more recent custom AI ISA that had that too. Pretty neat; I'm surprised it's not more common. I guess it doesn't matter so much now that memory is so much slower than ALU ops.
I'm interested in the implications for the open source movement, specifically about security concerns. Anyone know is there has been a study about how well Claude Code works on closed source (but decompiled) source?
I’ve had Claude Code diagnose bugs in a compiler we wrote together by using gdb and objdump to examine binaries it produces. We don’t have DWARF support yet so it is just examining the binary. That’s not security work, but it’s adjacent to the sorts of skills you’re talking about. The binaries are way smaller than real programs, though.
> Claude Code works on closed source (but decompiled) source
Very likely not nearly as well, unless there are many open source libraries in use and/or the language+patterns used are extremely popular. The really huge win for something like the Linux kernel and other popular OSS is that the source appears in the training data, a lot. And many versions. So providing the source again and saying "find X" is primarily bringing into focus things it's already seen during training, with little novelty beyond the updates that happened after knowledge cutoff.
Giving it a closed source project containing a lot of novel code means it only has the language and it's "intuition" to work from, which is a far greater ask.
I’m not a security researcher, but I know a few and I think universally they’d disagree with this take.
The llms know about every previous disclosed security vulnerability class and can use that to pattern match. And they can do it against compiled and in some cases obfuscated code as easily as source.
I think the security engineers out there are terrified that the balance of power has shifted too far to the finding of closed source vulnerabilities because getting patches deployed will still take so long. Not that the llms are in some way hampered by novel code bases.
> The llms know about every previous disclosed security vulnerability class and can use that to pattern match
Do the reports include patterns that could be matched against decompiled code, though? As easily as they would against proper source? I find it a bit hard to believe.
Many vulnerabilities aren't just pattern matching though; deep understanding of the context in the particular codebase is also needed. And a novel codebase means more attention than usual will be spent grepping and keeping the context in focus. Which will make it easier to miss certain things, than if enough of the context was already encoded in the model weights.
Same thing applies to humans: the better someone knows a codebase, the better they will be at resolving issues, etc.
Definitely not my wheelhouse, but I would expect it to be considerably worse.
Simply because the source code contains names that were intended to communicate meaning in a way that the LLM is specifically trained to understand (i.e., by choosing identifier names from human natural language, choosing those names to scan well when interspersed into the programming language grammar, including comments etc.). At least if debugging information has been scrubbed, anyway (but the comments definitely are). Ghidra et. al. can only do so much to provide the kind of semantic content that an LLM is looking for.
I've cut-and-pasted some assembly code into the free version of ChatGPT to reverse engineer some old binaries and its ability to find meaning was just scary.
Yesterday, i had claude decompile and fix firmware for my new samsung viewfinity s8 - there was really annoying pop up banner on each wake which you cant turn off, and samsung clearly didnt care. I was about to return it, then thought - hhmm, why not :) Not one-shotted, took several tries (lucky none of them bricked it, haha). Also i guess warranty is voided, but idc :)
It would be much more interesting/efficient if the LLM had tokens for machine instructions so extracting instructions would be done at tokenizing phase, not by calling objdump.
But I guess I'm not the first one to have that idea. Any references to research papers would be welcome.
Now imagine how much more it could have derived if I had given it the full executable, with all the strings, pointers to those strings and whatnot.
I've done some minor reverse engineering of old test equipment binaries in the past and LLMs are incredible at figuring out what the code is doing, way better than the regular way of Ghidra to decompile code.
On the subject of the weights and measures to check that a pint is a pint, I remember the father of a friend of mine at university who was responsible for the weights and measures for Staffordshire. I think he was the undersheriff or something like that, and that the official pint was part of the collection.
This would have been in the late 80s - i've no idea if it was still in use, but i've a feeling that the law hadn't necessarily moved on, so I guess the official measure could have been required if challenged in court.
The older Tektronix TDS540 series did this, but at much lower rates as was common in those days though. Internally there are differential feeds from the very beautiful hybrid ceramic input boards to 4 DACs, with some clever switching so that a single input can be sampled by all 4 DACs with a suitable offset to create 4x the sample rate when running with all 4 inputs.
The calibration procedure on the scope fiddles with the time alignment to get the different DACs correctly offset so that the combined signal is correct.
The hybrid ceramic input boards in their metal cases are a thing of beauty, fragile (don't ask how I know), but beautiful.
Yup, a lot of scopes actually did this internally and some still do. It's part of why some scopes lose half their BW when you go from 2 ports to 4 ports (some go the other direction and run multiple ports on one very fast ADC), they split the digitizers. It's just very very difficult to keep it working external to the box mainly because of line drift.
Back in the day, most Amiga games were made in Europe, so were tuned for 50Hz refresh, and played 'normally' over here. At 60hz, they would feel wrong, and likewise, US games could feel sluggish and unsatisfying.
Locking the machine to the monitor refresh rate in this way was a curse but also a blessing. It allowed the machines to drive a standard television in various regions, although the image was somewhat blurry, but also the use of dedicated monitors once you started taking it seriously. This reduced the price point and allowed lots of kids to convince their parents to get one :) The higher resolution modes just didn't work out though, so lots of stuff targeted the 320x256 resolution.
The surprise benefit was also that the amiga could genlock to a TV signal and used to overlay onto a video feed, leading to many interesting cheap graphics overlay capabilities which were otherwise totally unobtainable by small TV channels or enthusiasts.
Those were happy days.
reply