Given this was backdoor was likely funded by a nation-state actor and very carefully obfuscated, the fact that it was discovered within a month and never rolled out to production releases, shows that the open source process mostly worked. Not saying it couldn't be better.
I kinda disagree. This was luck. A dev on an unrelated project happened upon it and was diligent enough to dig in. A single change to any number of variables would have meant disaster.
I worked at a company that got red teamed. The pen testers were inside the network and were only found by a random employee who happened to be running little snitch and got a weird pop-up
Nobody celebrated the fact that the intrusion was detected. It was pure luck, too late, and the entire infosec leadership was fired as a result.
Like this xv issue, none of the usual systems meant to detect this attack seemed to work, and it was only due to the diligence of a single person unrelated to the project was it not a complete show.
Interesting investigation, good read. Definitely illustrates how new paradigms (i.e. overlay filesystems) can subtly affect behaviors in ways that are complex to track down.
If the software development is mostly stock and standard, I would agree, it doesn't matter if staff have advanced degrees or not. But developers with advanced degrees tend to have a better understanding of what is actually going on "under the hood". When tackling unorthodox, challenging problems, they use that knowledge to make better design decisions, resulting in better quality software. For those types of cutting-edge projects, I think there is value to having staff with advanced degrees.
I hope Boom can make supersonic travel commercially viable. Would be nice to have a faster option for long-distance travel, even though the cost will probably be too high for most. I'd sooner spend money on that vs. edge-of-space tourism (a la Virgin Galactic/SpaceX).
As advertising is a huge part of Google's revenue, I'm surprised they've let an extension like uBlock Origin work on Chrome as long as they have. If you haven't tried Firefox in a while, it's time to give it another shot. If I could get Firefox to handle Microsoft Teams meetings, I wouldn't even have Chrome installed.
Your point about Firefox not handling the Microsoft teams meeting is interesting as I have found that google meet video calls do not work well or at all on Firefox the last time I checked. my best guess is that this is Google trying to entrench their browser.
I've been using Brave and I recently decided to give Firefox another shot. I clicked an Amazon affiliate link from one of Ben Vallack's YouTube videos for a handheld espresso maker. Next thing I know I'm getting tons of ads on Facebook for espresso machines. This was with uBlock installed.
I never, _ever_ had that happen with Brave. In the context of Google deprecating MV2 - Brave's shields are hard coded into the Chromium chromebase and do not rely on MV2 or MV3 [1]. They will also be continuing support for MV2 extensions.
I'll push back and say Brave is far more than just Chrome with a different skin. Remember, it is a fork. That description is better suited for Edge or Opera.
I hear you on the concern about the browser engine monoculture. It's a concern of mine as well.
I guess what I should say is - _if_ you're going to use a chromium-based browser, you should probably use Brave.
I'm currently looking into Mull browser as it seems to be to Firefox what Brave is to Chrome.
Yea, Brave is a fork. And I might actually use it if it weren't for the "we change affiliate links to our own" and the crypto stuff.
Might actually give it a second chance now that Google is clearly hamstringing non-chromium browsers on Youtube. Both Firefox and Safari on macOS have weird loading issues - and I pay for Premium so it can't be adblock related.
Fork term is too vague as to become almost meaningless. Technically cloning software and changing a single letter anywhere is "a fork". It's just pointless for endusers.
So going by a letter of definition Chrome Brave is a fork, but by spirit of a definition it is not. Example - this topic right here. If it was a fork they would have forked the code and maintained Manifest V2 instead of V3, autonomously, like software forks do. But since it is not a real fork (in spirit of the word), they can't and don't want to do it, and simply accept every change the real code owner does, Google in this case.
Mozilla is a non-profit that made a couple of poor decisions. Meanwhile Google's whole business model is based around it. It is not the same thing at all.
Newer tech has added vast amounts of new functionality at the expense of making the older, simpler functions a bit more cumbersome and less reliable. Not everyone likes that trade off. For example, in the early 1980s, landline phones were indestructible, never dropped calls, and had 99.99999% uptime. VOIP phones are nowhere near that reliable, by any metric, but that's the price for fully redirect-able calls/messages among dozens of other useful features.
I've gone back to analogue radios, amps from the 90s, physical notebooks, old TVs, and even old gaming consoles. Why? Because they just work. There are no updates changing the interface, bugs to workaround (usually also from updates), annoyingly poor interface design to deal with, and the need to handle them with cotton in case they break.
That old tech is so good, it works 30+ years later. Can you imagine anything you bought in the last year working thirty years from now?
I understand where you are coming from. I've done that with a few selected items, but in general I have chosen the newer options. There always seems to be a feature or two I find worth the hassle.
C and C++ are most often used when performance is the highest priority. Undefined behavior is basically the standards committee allowing the compiler developers maximum flexibility to optimize for performance over error checking/handling/reporting. The penalty is that errors can become harder to detect.
It appears the author is a Go advocate. I assume they are valuing clearly defined error checking/handling/reporting (the authors definition of correctness) over performance. If that's what you are looking for, consider Go.
"Oh, it was quite a while ago. I kind of stopped when C came out. That was a big blow. We were making so much good progress on optimizations and transformations. We were getting rid of just one nice problem after another. When C came out, at one of the SIGPLAN compiler conferences, there was a debate between Steve Johnson from Bell Labs, who was supporting C, and one of our people, Bill Harrison, who was working on a project that I had at that time supporting automatic optimization...The nubbin of the debate was Steve's defense of not having to build optimizers anymore because the programmer would take care of it. That it was really a programmer's issue.... Seibel: Do you think C is a reasonable language if they had restricted its use to operating-system kernels? Allen: Oh, yeah. That would have been fine. And, in fact, you need to have something like that, something where experts can really fine-tune without big bottlenecks because those are key problems to solve. By 1960, we had a long list of amazing languages: Lisp, APL, Fortran, COBOL, Algol 60. These are higher-level than C. We have seriously regressed, since C developed. C has destroyed our ability to advance the state of the art in automatic optimization, automatic parallelization, automatic mapping of a high-level language to the machine. This is one of the reasons compilers are ... basically not taught much anymore in the colleges and universities."
-- Fran Allen interview, Excerpted from: Peter Seibel. Coders at Work: Reflections on the Craft of Programming
Hum... We have to move further than this citation. The 1980s C was much more secure than our current one.
The undefined behavior paradoxes were only added by the 90s, when optimizing compilers became a logic inference engine, feed with the unquestionable truth that the developer never exercises UB.
Just because it was a sane language for kernel development at the 1980s, it doesn't mean it is one now.
Yeah, because the only way to achieve performance in 1980's C on 16 bit platforms was to litter it with inline Assembly, thus UB based optimisation was born to win all those SPEC benchmarks in computer magazines.
Funny thing in that the same thing stopping people to write those crazy¹ optimizers at the 1980s was exactly the lack of capacity of the computers to run them.
What means that they appeared exactly at the time the need for them became niche. And yet everybody adopted them due to the strong voodoo bias we all have at computer-related tasks.
1 - They are crazy. They believe the code has no UB even though they can prove it has.
A great quote. She's absolutely right. C has absolutely polluted people's understanding of what compiler optimizations should be. Compilers should make a program go faster, invisibly. C makes optimization everyone's problem because the language is so absolutely terrible at defining its own semantics and catching program errors.
The undefined behavior I struggle with keeps me from better performance though. I have something like [(uint32_t value) >> (32 - nbits)] & (lowest nbits set). For the case of nbits=0, I would expect it to always return 0, even if the right shift of a 32-bit value by 32 bits is undefined behavior, then bit-wise and with 0 should make it always result in 0. But I cannot leave it that way because the compiler thinks that undefined behavior may not happen and might optimize out everything.
Exactly. The irony in all of this is that C is not a portable assembler. It'd be better if it were[1]!
If you want the exact semantics of a hardware instruction, you cannot get it, because the compiler reasons with C's abstract machine that assumes your program doesn't have undefined behavior, like signed wraparound, when in some situations you in fact do want signed wraparound, since that's what literally every modern CPU does.
[1] If the standard said that "the integer addition operator maps to the XYZ instruction on this target", that'd be something! But then compilers would have to reason about machine-level semantics to make optimizations. In reality, C's spec is designed by compiler writers for compiler writers, not for programs, and not for hardware.
I think that the undefinde behaviour should be partially specified. In the case you describe, it should require that it must do one of the following:
1. Return any 32-bit answer for the right shift. (The final result will be zero due to the bitwise AND, though, regardless of the intermediate answer.) The intermediate answer must be "frozen" so that if it is assigned to a variable and then used multiple times without writing to that variable again then you will get the same answer each time.
2. Result in a run-time error when that code is reached.
3. Result in a compile-time error (only valid if the compiler can determine for sure that the program would run with a shift amount out of range, e.g. if the shift amount is a constant).
4. Have a behaviour which depends on the underlying instruction set (whatever the right shift instruction does in that instruction set when given a shift amount which is out of range), if it is defined. (A compiler switch may be provided to switch between this and other behaviours.) In this case, if optimization is enabled then there may be some strange cases with some instruction sets where the optimizer makes an assumption which is not valid, but bad assumptions such as this should be reduced if possible and reasonable to do so.
In all cases, a compiler warning may be given (if enabled and detected by the compiler), in addition to the effects above.
I wanted to reply that your point 3 should already be possible with C++ constexpr functions because it doesn't allow undefined behavior. But I it seems I was wrong about that or maybe I'm doing it wrong:
The first output will print a random number, 140728069214376 in my case, while the second line will always print 1. However, when I put the ( ( 1ULL << nBits ) - 1U ) part into a separate function and print the values for that, then getBits( 0 ) suddenly always returns 0 as if the compiler understands suddenly that it will and with 0.
In this case, the compiler will only print a warning when trying to call it with getBits2<0>. And here I kinda thought that constexpr would lead to errors on undefined behavior, partly because it always complains about uninitialized std::array local variables being an error. That seems inconsistent to me. Well, I guess that's what -Werror is for ...
Unfortunately constexpr doesn't imply constant evaluation. Your function can still potentially be executed at runtime.
If you use the result in an expression that requires a constant (an array bound, a non-type template parameter, a static_assert, or, in c++20, to initialize a constinit variable), then that will force constant evaluation and you'll see the error.
Having said that, compilers have bugs (or simply not fully implemented features), so it is certainly possible that both GCC and clang will fail to correctly catch constant time evaluation UB in some circumstances.
Eh. The existence of Rust (and Zig, to a lesser extent) prove that you can, in fact, have both: Highest performance and safe, properly error checked code without any sort of UB.
UB is used for performance optimizations, yes, but all of these difficult to diagnose UB issues and bugs happen because C++ makes it laughably easy to write incorrect code, and (as shown by Rust) this is by no means a requirement for fast code.
The Computer Language Benchmarks Game has C++ outperforming Rust by around 10% for most benchmarks. Binary trees is 13% faster in C++, and it's not the best C++ binary tree implementation I've seen. k-nucleotide is 32% faster in C++. Rust wins on a few benchmarks like regex-redux, which is a pointless benchmark as they're both just benchmarking the PCRE2 C library, so it's really a C benchmark.
> because C++ makes it laughably easy to write incorrect code
I was going to ask how much you actually program in C++, but I found a past comment of yours:
> I frankly don't understand C++ well enough to fully judge about all of this
> Rust wins on a few benchmarks like regex-redux, which is a pointless benchmark as they're both just benchmarking the PCRE2 C library, so it's really a C benchmark.
The Rust #1 through #6 entries use the regex crate, which is pure-Rust. Rust #7[rust7] (which is not shown in the main table or in the summary, only in the "unsafe" table[detail]) uses PCRE2, and it is interestingly also faster than the C impl that uses PCRE2[c-regex] as well (by a tiny amount). C++ #6[cpp6], which appears ahead of Rust #6 in the summary table (but isn't shown in the comparison page)[comp], also uses PCRE2 and is closer to Rust #7.
I mean, it's outperforming C as well in that particular benchmark.
Lies, damn lies, and benchmarks?
I can at least say, the performance difference between C, C++, and Rust, is splitting hairs.
If you want to write something performant, low level, with predictable timing, all three will work.
I'm spending a lot of time building projects with Rust & C++ these days. The issue/tradoff isn't performance with C++, but that C++ is better for writing unsafe code than Rust.
> C++ makes it laughably easy to write incorrect code
It also provides a lot of mechanisms and tools to produce correct safe code, especially modern C++. Most codebases you're not seeing a lot of pointer arithmetic or void pointer or anything of that nature. You hardly even see raw pointers anymore, instead a unique_ptr or a shared_ptr. So yes, you can write incorrect code because it's an explicit design goal of C++ not to treat you like a baby, but that doesn't mean that writing C++ is inherently like building a house of cards.*
You can do any rust optimization yourself in C++ (ie. aliasing assumptions), whereas rust makes the other way around very difficult, often forcing you to use multiple layers of indirection where c++ would allow a raw pointer, or forcing an unwrap on something you know is infallible when exceptions would add no overhead, etc. Rust programmers want people to believe that whatever appeases the supposedly zero cost borrow checker is the fastest thing to do even though it has proven to be wrong time and time again. I can’t tell you how many times I’ve seen r/rust pull the “well why do you want to do that” or “are you sure it even matters” card every time rust doesn’t allow you to write optimized code.
> You can do any rust optimization yourself in C++ (ie. aliasing assumptions)
I don’t think that is entirely true. C++ doesn’t have any aliasing requirements around pointers, so if the compiler sees two pointers it has to assume they might alias (unless the block is so simple it can determine aliasing itself, which is usually not the case), but in Rust mutable references are guaranteed to not alias.
This was part of the reason it took so long to land the “noalias” LLVM attribute in Rust. That optimization was rarely used in C/C++ land so it had not been battle tested. Rust found a host of LLVM bugs because it enables the optimization everywhere.
While standard C++ has no equivalent of a noalias annotation, it's wrong to say that it has no aliasing requirements. To access an object behind a pointer (or a glvalue in general), the type of the pointer must be (with a few exceptions) similar to the type of the pointee in memory, which is generally the object previously initialized at that pointer's address. This enables type-based alias analysis (TBAA) in the compiler, where if a pointer is accessed as one type, and another pointer is accessed as a dissimilar type, then the compiler can assume that the pointers don't alias.
Meanwhile, Rust ditches TBAA entirely, retaining only initialization state and pointer provenance in its memory model. It uses its noalias-based model to make up for the lack of type-based rules. I'd say that this is the right call from the user's standpoint, but it can definitely be seen as a tradeoff rather than an unqualified gain.
Because existing unsafe code written in stable Rust depends on the ability to convert raw pointers and references from one type to another, as long as their memory layouts match. That's the whole premise of the bytemuck crate [0], and it's the basis for things like the &str-to-&[u8] or &[u8]-to-&str conversions in the standard library.
That is correct. However, raw pointers are not borrow checked, in safe Rust they're largely useless, but in unsafe Rust you can use raw pointers if that's what you need to do to get stuff done.
As an example inside a String is just a Vec<u8> and inside the Vec<u8> is a RawVec<u8> and that is just a pointer, either to nothing in particular or to the bytes inside the String if the String has allocated space for one or more bytes - plus a size and a capacity.
The argument for the Exception price is that we told you Exceptions were for Exceptional situations. This argument feels reasonable until you see it in context as a library author.
Suppose I'm writing a Clown Redemption library. It's possible to Dingle a Clown during redemption, but if the Clown has already dingled that's a problem so... should I raise an exception? Alice thinks obviously I should raise an exception for that, she uses a lot of Clowns, the Clown Redemption library helps her deliver high quality Clown software and it's very fast, she has never dingled a Clown and she never plans to, the use of exceptions suits Alice well because she can completely ignore the problem.
Unfortunately Bob's software handles primarily dingling Clowns, for Bob it's unacceptable to eat an exception every single damn time one of the Clowns has already been dingled, he demands an API in which there's just a return value from the dingling function which tells you if this clown was already dingled, so he can handle that appropriately - an exception is not OK because it's expensive.
Alice and Bob disagree about how "exceptional" the situation is, and I'm caught in the middle, but I have to choose whether to use exceptions. I can't possibly win here.
Like I said, this argument doesn’t work because you can use options in c++ but you can’t use exceptions in rust. So when there’s an occasion where you want to avoid the overhead of an option or result in rust - well too bad.
If all you care about is outright performance, having the option for exceptions is easily the superior choice. The binary does get bigger but those are cold pages so who cares (since exceptions are exceptional, right?)
Expanding this for those not familiar with Go's history: Ken Thompson, formerly of Bell Labs and co-creator of B and Unix, was deeply involved in Go's early days. Rob Pike, also ex-Bell Labs, was also one of Go's principal designers.
I can't find a source now, but I believe Rob described Russ Cox as "the only programmer I've met as gifted as Ken." High praise indeed.
Go it's modelled after plan9'C [1-9]c = go cross compiling, and Limbo so it has a lot of sense. Both come from the same people after all. Unix->Unix8 -> Plan9 -> Go.
I suspect that there is some huge number of developer hours that have been wasted, and huge amount of money wasted, on cleaning up after security breaches and finding and fixing security issues. I suspect that those numbers dwarf any losses that might have arisen due to reduced developer productivity or reduced performance when using a (hypothetical) C-like language that doesn't allow the compiler to do these sorts of things.
"a (hypothetical) C-like language that doesn't allow the compiler to do these sorts of things."
It's not very hypothetical in 2023. There are plenty of languages whose compilers don't do this sort of thing and attain C-like performance. There isn't necessarily a single language that exactly and precisely replaces C right now, but for any given task where you would reach for C or C++ there's a viable choice, and one likely to be better in significant ways.
I also feel like this is missed by some people who defend this or that particularly treatment of a particular undefined behavior. Yeah, sure, I concede that given the history of where C is and how it got there and in your particular case it may make sense. But the thing is, my entire point is we shouldn't be here in the first place. I don't care about why your way of tapping a cactus for water is justifiable after all if you consider the full desert context you're living in, I moved out of the desert a long time ago. Stop using C. To a perhaps lesser but still real extent, stop using C++. Whenever you can. They're not the best option for very many tasks anymore, and if we discount "well my codebase is already in that language and in the real world I need to take switching costs into account" they may already not be the best option for anything anymore.
I assume you're referring to "my codebase is already in C/C++"? Which I did acknowledge?
Because otherwise, what the world is increasingly not aligning with is using C when you shouldn't be. Security isn't getting any less important and C isn't getting any better at it.
This reasoning is why software keeps getting slower and more bloated, build times increase, and latency goes up despite having orders of magnitude more compute power.
If whatever language you're thinking of does that, it isn't one of the ones I'm talking about. I sure as heck aren't talking about Python here. Think Rust, D, Nim, in general the things floating along at the top of the benchmarks (that's not a complete list either).
I don't see how this solves anything, Nim's backend is C, which means it should suffer from the same pitfalls. They probably clean it up and eliminate UB, but it should still exist.
Yeah, but with less bugs, more features, and faster development. I mean I hate Electron with a passion but it means everybody gets a client. Let’s not pretend that it’s all worse rather than a set of tradeoffs.
You don't need one faster. You need one as fast. These options generally exist. Rust seems to have crept its way right up to "fast as C"; it isn't really a distinct event, but https://benchmarksgame-team.pages.debian.net/benchmarksgame/... (it tends to better on the lower ones so scroll a bit). There are some other more exotic options.
C isn't the undisputed speed king any more. It hasn't necessarily been "roundly trounced", there isn't enough slack in its performance for that most likely, but it is not the undisputed speed king. It turns out the corners it cuts are just corners being cut; they aren't actually necessary for performance, and in some cases they can actually inhibit performance. See the well-known aliasing issues with C optimizations for an example. In general I expect Rust performance advantages to actually get larger as the programs scale up in size and Rust affords a style that involves less copying just to be safe; benchmarks may actually undersell Rust's advantages in real code on that front. I actually wouldn't be surprised that Rust is in practice a straight-up faster language than C on non-trivial code bases being developed in normal ways; it is unfortunately a very hard assertion to test because pretty much by definition I'm talking about things much larger than a "benchmark".
Yes, but then complier vendors started abusing UB to increase performance and while silently decreasing safety/correctness. If the compiler creates a security bug by optimizing away a bounds check the programmer explicitly put there, that's a problem.
Very impressive. If my understanding of how the AI works is correct, it is using a pre-computed strategy developed by playing trillions of hands, but it is not dynamically updating that during game play, nor building any kind of profiles of opponents. I wonder if by playing against it many times, human opponents could discern any tendencies they could exploit. Especially if the pre-computed strategy remains static.
We played 10,000 hands of poker over the course of 12 days in the 5 humans + 1 AI experiment, and 5,000 hands per player in the 1 human + 5 AI's experiment. That's a good amount of time for a player to find a weakness in the system. There's no indication that any of the players found any weaknesses.
In fact, the methods we use are designed from the ground up to minimize exploitability. That's a really important property to have for an AI system that is actually deployed in the real world.