Hacker Newsnew | past | comments | ask | show | jobs | submit | wahern's commentslogin

On modern systems, including Linux and OpenBSD, getentropy will return entropy seeded from not just rdrand/rdseed, but other entropy sources on the system, e.g. many Intel NICs, the AMD PSP, etc, and at least on Linux some well-vetted jitter hacks as a backstop for embedded devices without hardware RNGs. These sources are unavailable to user space. You have nothing to lose and everything to gain by using getentropy as one source, among whatever else you're using, to seed your PRNG.


> the first VPN that can’t log your activity and outsmarts internet censorship.

I guess they never heard of Zero Knowledge Systems: https://en.wikipedia.org/wiki/Zero_Knowledge_Systems


Pretty sure Carl has heard of them, having worked for Adam Back with me at Blockstream...

You don't know if it's bad. Microcode updates might fix it, or break it for that matter. Revision history can be difficult if not impossible to comprehensively catalog.

What it is is unreliable. And that's fine so long as you have other entropy sources. OpenBSD is really good about this. Quite a few drivers for various chipsets and cards exist just to read their RNGs, not actually use them for their primary function (which can be a bummer if you want to use the the device, get your hopes up when you see the driver exists in the tree, then discover the only capability it supports is reading the RNG). If you have a CPU with a known bad rdrand, odds are OpenBSD is still sourcing strong randomness from some other chip in your system (PSP, NIC, etc). And because feeding bad (as opposed to malicious[1]) entropy is harmless[2], they don't have to maintain a pile of conditions. Nobody is worse off, and overall everybody is better off, including having stronger getrandom/getentropy output, by not trying to be clever.

[1] https://blog.cr.yp.to/20140205-entropy.html

[2] Presuming nothing is relying on an entropy estimator. I can't remember if Linux finally moved past the entropy estimator nonsense. IIRC they did add a software jitter RNG that runs early to try to set a minimum entropy floor, regardless of hardware sources.


> that's fine so long as you have other entropy sources

Well, if you literally have nothing else, then you don't have an option anyway, so the whole question is moot.

Except yeah if literally the only way to collect entropy in your system is the platform's opaque RNG, then sure this means your risk assessment should list that as a SPOF. But by definition these cases only have that option, so you can't do anything else.

In reality, you can probably do something else in all but the most extreme embedded environments.


Illegal immigration didn't really exist in 1850, nor even a meaningful Federal immigration policy beyond Congress statutorily setting the number of years of residency required to apply for citizenship, and of course limiting it to whites. The borders were open; border checkpoints and ports of entry were years away. People came, worked, and after several years applied for citizenship at the local state court house, attesting to the number of years they'd been here.

Federal immigration policy didn't really get going until the 1870s (notwithstanding some toothless laws passed in the 1860s), and IIRC it wasn't until the 1880s that the Federal government created ports of entry to process immigrants, including creating a system to reject immigrants.

I mention this because I think the ~20 year time lag between the first wave of large scale immigration and immigration becoming a major political issue, including the rise of anti-immigration sentiments and policies, is notable. Not necessarily for anything specific, just that it's interesting how history rhymes.


The FISA court only handles warrants, not prosecutions. Generally the only parties involved in a warrant request to a court (FISA or any other), are the judge and the government representative(s) seeking the warrant, though the court can pull in third-parties if it wants, e.g. for advice on legal questions.

A secret criminal prosecution trial would be unconstitutional under the Sixth Amendment ("In all criminal prosecutions, the accused shall enjoy the right to a speedy and public trial"). Though SCOTUS has unfortunately allowed for some classified material to be kept secret, sometimes (IIRC) even from the defendant and the jury, though usually this is ancillary stuff, not the direct evidence that would secure a conviction.


Warrants are what's at issue when the 4th amendment is in play. The thing you can't do when FISA issues a warrant is get the details about that warrant, who issued it, why it was issued. You can't even know that a FISA warrant was deployed against yourself.

Normal judicial warrants are public record.


The US was a net exporter for most of the time it was a de jure and de facto reserve currency, up until the dollar was floated (i.e. end of gold convertibility). IOW, it was arguably the reserve currency status that drove the trade deficits, not the other way around. The effect was muted by the artificial constraint of gold convertibility, but the pressures from that restraint manifested in other problems, culminating in a currency crisis and Nixon's decision to end convertibility. That unleashed the deflationary pressures caused by reserved status, accelerating the shift to goods imports.

> The US was a net exporter for most of the time it was a de jure and de facto reserve currency, up until the dollar was floated (i.e. end of gold convertibility).

No, the US ran a goods surplus but a current account deficit.

If we look at US history, we can divide it into a few periods:

revolution to civil war: US is a net debtor, running goods deficits and borrowing from europe.

1870 - Great Depression/start of WW2: US is running goods surpluses (of about 1% of GDP) but continues to heavily borrow from Europe.

Post WW2 - Bretton woods. US is suddenly the world's biggest creditor, and it's good surplus rapidly declines to a deficit, forcing the US off the gold standard.

That middle period puzzled a lot of economists, because the balance of payments identity says that the current account plus capital account must sum to zero, so if the capital account is positive (continues to borrow from Europe) then the current account must be negative. But here we have reports of the US running a goods surplus! Well, the problem is that even though there was a goods surplus, the current account was negative. The US owed so much money in dividend and interest payments that the money earned from the goods surplus wasn't enough and the US kept getting deeper in debt to Europe throughout this period, which meant that Europe was a net accumulator of US liabilities even though the US ran a goods surplus.

And it was the fact that Europe kept accumulating US liabilities that allowed the these liabilities to be traded as an effective reserve currency. If the US was not getting its liabilities into the hands of the europeans, then there is no way US liabilities could possibly be used to settle international trade. This should be a no brainer.

Now a lot of crazy stuff happened during the stock market crash and capital flight during WW1 and WW2 that turned the table on the Europe, so the US ended up in a situation where, very suddenly -- as in, over the course of just a few days -- it became a net creditor to Europe, but that necessitated the Marshall plan, where the US needed to flood Europe with dollar claims -- which were gifts, not investments -- in order to prevent the European economies from grinding to a halt in the immediate aftermath of WW2, and then the US goods trade deteriorated so that we had to go off the gold standard. Thus the period from the end of WW2 to the end of Bretton woods should be viewed as an anomalous disequilibrium period of adjustment, and if you look at US current account data, you see a fairly rapid decline because the US goods surplus during the WW2 period was artificially inflated by counting munitions and war material as exports, but these exports were "paid" for by loans that were forgiven, and this, together with the Marshall plan, is what screws with traditional readings of the balance of payments identity in that WW2-end of Bretton woods period.

Bottom line, if you want to run trade surpluses and be a reserve currency, you need to be giving away more claims than are necessary to buy your goods, because at the end of the day, the rest of the world has to be a net accumulator of claims on you in order for those claims to be a reserve currency.


Huh, interesting. During the ZIRP period (~2016-2017), I remember suggesting somewhat tongue-in-cheek here that the rational response to negative interest rates is to borrow as much money as possible, use it to raise an army, and then go conquer the people who loaned you the money. It sounds like that is actually what the U.S. did, except with a dose of diplomacy that got them to exhaust themselves fighting each other first.

It's kind of the historical embodiment of "Owe the bank $100, and you have a problem. Owe the bankers $100T, and they have a problem."


According to this the US nominally ran a current account surplus until the 1970s: https://www.bea.gov/sites/default/files/2024-03/trans423-ann...

Which is what you'd expect if you're exporting more than importing. Perhaps you meant by some other accounting, which is what I was alluding to regarding the distortion caused by gold convertibility. Ceteris paribus, the trade & account balance will zero out over time in the absence of some kind of regulatory or similar distortion. China doesn't need to be a net importer to become a reserve currency, but if they became one (deliberately or otherwise) and allowed their currency to float than in time their exports would tend to fall and they'd likely become a net importer, at least if they became the dominant reserve like the US. (If they didn't float, you'd get a mess like the US had.) The reserve status can drive the balance of trade toward net importing. Which is precisely why China doesn't really want to become a global reserve currency. They'd certainly like the soft power that would bring, but they don't want the domestic employment disruption the US suffers from. That doesn't stop them from wanting their cake and eating it, too; they can try, but nobody really believes they could, so nobody takes it seriously. Though it's not necessarily an all or nothing deal. I'm not sure the world needs a single reserve currency as dominant as the dollar. We have markets to arbitrage and balance currency valuations, including future expected valuations. A singular dominant reserve currency is helpful to reduce friction, but less so when you have huge, global currency and currency derivatives markets constantly trading.


The Louvre Accord and the Plaza Accord before that, in the 80's, are of a different time and place.

> When both conditions are met, the loop body is replaced with a call to std::this_thread::yield(). This gives execution of the loop the forward-progress semantics it previously lacked.

That's the epitome of the hidden code downside that Linus and many others dislike about C++. For constructors and destructors it's somewhat unavoidable and not so random, though Rust does better at limiting the blast radius of non-local code, at least in the drop case.

If they didn't want to adopt the C11 rule, the C++ committee should've explored a rule that required the compiler to emit a diagnostic or error for trivial loops (whether as defined by C11 or otherwise), requiring the programmer to explicitly insert ::yield or similar. No hidden code, and less opportunity for the compiler to do surprising things.

The C committee has been rigorously enumerating UB cases in the standard and addressing each case in turn, often by requiring a diagnostic, error, or by turning it into implemention defined behavior. But inserting code like that would be unthinkable.


The C++ committee has a habit of thumbing its nose at standard practice. They intentionally broke bitwise operators on volatiles because they wanted to be impose their atomic religion everywhere. Then they had to walk that back after they broke every embedded library directly manipulating hardware registers.

Empty infinite loops are also commonplace in embedded C once main is done with init and within exception handlers. They don't care about anything beyond their narrow systems programming worldview.


By narrow luck compiler writers so far have been the sane bunch, and have ignored C++ committee on many important points. Thus we still have explicitly non-conformant things like -fno-exceptions that lets one use C++ compiler on embedded.

But I wonder how long that can last, with the way C++ is going.

At one point, it will make practical sense to update codebase to some other language, rather than keep fighting this one


I have been saying that C++23, or maybe C++26 due to reflection, will eventually be the last standard that actually matters.

For a large number of C++ users, it boils down to what it offers beyond C, but not to the extent WG21 is driving it since C++20.

Also the major surviving three compilers have lost wind on their sails as the corporations sponsoring their development have switched focus to other compiled languages.

Other than the whole security debate, there are no features that would make C++ significantly better for LLVM, GCC, CLR, V8, CUDA,.. improvements.

In fact, some of those projects still require C++17.

If this sounds strange, how many care nowadays about ISO Fortran 2023, or ISO COBOL 2023, despite the amount of software written in them powering many busisesses, or Python libraries even, e.g. SciPy.

Or even with C, almost 20 years later many still reach out to C99, ignoring everything else.


Not to take away from your points; SciPy is now Fortran-free completely[0] (we are also requiring C++17 at most). NumPy never had it. BLAS is all C/Assembly in all optimized vendors. For LAPACK we are working on it [1].

Once there is enough pain, none of the talking points matter for any language. They don't and can't die but linger. I fear that time for C family might come in a decade which would be a shame given how magical Cpp compilers are, all that effort folks pouring in.

[0]: https://github.com/scipy/scipy/issues/18566 [1]: https://github.com/ilayn/semicolon-lapack


Thanks for the update overview, and interestingly you also mention C++17, as the version you currently care about.

> They intentionally broke bitwise operators on volatiles because they wanted to be impose their atomic religion everywhere.

Could you elaborate on this?


"broke" is arguably an overstatement. C++20 deprecated some (most?) operations on volatile variables [0] in part because they can misleadingly imply an atomic operation:

> volatile external modifications are only truly meaningful for loads and stores. Other read-modify-write operations imply touching the volatile object more than once per byte because that’s fundamentally how hardware works. Even atomic instructions (remember: volatile isn’t atomic) need to read and write a memory location []. These RMW operations are therefore misleading and should be spelled out as separate read ; modify ; write, or use volatile atomic operations which we discuss below.

This was not received particularly well in the embedded community (e.g., [1]) due to said deprecation affecting compound bitwise operations on volatile variables, which are extremely widely used to interact with hardware registers. This pushback eventually resulted in C++23 un-deprecating compound bitwise operators on volatile variables [2].

[0]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p11...

[1]: https://www.reddit.com/r/cpp/comments/jswz3z/compound_assign...

[2]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p23...


Basically on a rare occasion somebody (JF Bastien) was able to land a C++ proposal which warns you if you'd do something stupid, and inevitably C programmers cosplaying as C++ programmers insisted "it's not stupid it's advanced" and so now C++ must not even warn you this is a bad idea.

It still is a bad idea, but being warned would make them feel bad.


GNU C does the same: memory copies can be optimized into memcpy, various operations can be realized as calls into libgcc, etc.

And memcpy is kind of special to C/C++ compilers. Sure, it exists as a function, but it will often have special purpose code generated for that particular location.

It’s obvious why you want to inline memcpy, but the specialization is more interesting. For example, I’ve seen the compiler optimize a memcpy with a static number of bytes and then use SIMD registers to do the copying with no loop at all. It can even be smart enough to take advantage of memory alignment for this.


Those do for the most part correspond to operations which make sense in an embedded context, though.

> should've explored a rule that required the compiler to emit a diagnostic or error for trivial loops (whether as defined by C11 or otherwise), requiring the programmer to explicitly insert ::yield or similar

It wouldn't work when this kind of loop is generated by macros/templates in some unreachable case left after const folding.


If it's truly unreachable then it's not likely to be a problem. If it is reachable and it's emerging from some macros and templates then I would be more inclined want a warning for it.

Yeah, but then you need compiler to somehow know if it's truly unreachable to know when to emit the warning and when to not do that.

If it's a warning and not an error then emit it whether it's unreachable or not

It's catastrophic actually. Like disastrously catastrophic. It started with C++20 mostly, and has only kept getting worse from then. See zero initializing variables by default (WHY?) compare/meta including half the STL and HARDCODING those symbols, std::initializer_list being in the std namespace (if you don't include <initializer_list> you literally can't use it, and there is no such thing as a __initializer_list or some internal symbol), the entire coroutine library where you MUST provide coroutine_handle, noop_coroutine, suspends et al (coroutines aren't that bad because they're not necessarily spaghetti).

<meta> is the single WORST OFFENDER, where they hardcode std::vector (literally std::vector in the std namespace) std::ranges std::allocator.


I can't find anything saying variables are zero initialized by default in C++20. But the reason to do so is obvious: many bugs are caused by the lack of this, and as long as you can opt out with "= void" or something, it's not violating C++ core principles.

They were saying the problematic philosophy started in C++ 20, not the variable initialization rule.

Yes the reason is obvious, but it’s neither simple nor black and white. One huge problem is that this can cause serious performance regressions, and you have to change your code to opt out, e.g. add “[[indeterminate]]”. There are many, many cases in high performance computing where the intended & desired behavior is don’t touch my variables until I fill them.

This is changing C++ core principles, there’s a new designation for the state of a variable: erroneous. It’s also subtle and weird, because you can still have well-defined behavior even with erroneous state. It does seem like this might be an experiment though, I don’t think this is the end of the story. (It seems they’re already talking some redesign of this idea.)


What I'm most annoyed at with the variable initialization change is that:

  - It's potentially a performance change in every single function, especially ones that have sizable fixed-size buffers
  - If you have regressions you have to spray [[indeterminate]] everywhere, because there is no coarser way of suppressing it.
  - While the language says unrecognized attributes are ignored, compilers frequently warn on unrecognized attributes. Clang, for instance, currently warns on [[indeterminate]].
  - There is no defined macro name for backwards compatibility.
Which means that libraries are going have to all declare their own macros for [[indeterminate]] and pepper their code with it.

Uninitialized variables were already UB to read, because some architectures have trap representations, even for integers. Every register on Itanium has one.

That's assuming you were reading it without writing to it. There are three common cases when that isn't true.

The first is that you have a fixed buffer large enough for the maximum message size even though the typical ones aren't that big. You most often write 1% of the buffer and read it back, the other 99% is never accessed.

The second is that you always write the entire contents before reading it but the compiler may not be able to see that.

And the third is that you have a code path where that variable is simply not used.

You would then have the compiler emitting instructions to write zeros that are either overwritten before being read or are never read at all.

Moreover, zero initializing the data doesn't actually remove the bugs when that isn't the case. Consider the first case when you mess up. You have a fixed buffer used to store variable length messages. For the first message the buffer is now zeros instead of uninitialized, but for every subsequent message the remainder of the buffer still contains the remainder of the previous message and subjects you to information disclosure or data modification if you're reading back a different amount than was written in the associated call.

Now consider the second or third case. You unintentionally read from a variable before assigning to it. You get zeros instead of uninitialized memory, but if you weren't expecting zeros, well, the UID field is now 0.


If you are writing to it before reading from it then it's not uninitialised, and in 99% of cases the compiler can see that and will not set it to 0 at its declaration because that's a dead store.

Consider this (quite common) code:

  char buffer[LARGE_SIZE];
  if(maybe_fill_buffer(buffer, sizeof(buffer))) {
      use_result(buffer);
  }
The function maybe_fill_buffer() is an external library function that either fills the buffer and returns true or doesn't access it and returns false. Or maybe it unconditionally fills it, or unconditionally returns false without reading from it. The compiler can't see any of that though because it's in an external library. For all it knows that function is going to read from it instead of writing to it.

Notice that if it could actually figure it out 99% of the time then it could also emit a warning the 1% of the time that it can't and encourage you to make an explicit choice, which would have been a better option if that was actually the rate.


That's not reason enough to have the compiler initialize.

Zero initializing also hides bugs.

Say you have some code that should not be reading the initial state and is buggy if it does. Without zero-init, valgrind and msan will give you an immediate and false positive message that your code is wrong-- or forget dynamic analysis: the compiler can often statically tell you that the code will use an uninitialized variable. Zero initialize it and you lose that signal.


"valgrind and msan will give you an immediate and false positive message"

Why a false positive? Are you one of those people that 'knows' that their code is correct and always blames the tool? What you describe sounds like a real error. Sort of. The error is not triggered by reading the value, it gets triggered when it is used in some conditional context and the undefinedness has an observable impact on the execution of the program.

This is a change from undefined/erroneous behaviour to something else - defined but maybe not what you wanted.

I agree that this can make error analysis more difficult.


> See zero initializing variables by default

Strictly speaking the standard only requires some pattern that is not tied to program state. Zero works for that, but so do other static patterns like 0xABAB... or the like.

> (WHY?)

The motivation section of the corresponding paper [0] might be interesting. tl;dr: it lets wrong code be wrong without suffering from (all) the consequences of full-blown UB.

[0]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p27...


D initializes floating point variables to NaN by default. And chars to 0xFF. Yes it's controversial!

In other words, it's a sane default that you can opt out of on a case by case basis which is the way it should have been all along.

I’d guess the concern is performance, not what initializer value is used. And performance is a valid concern that is discussed in the proposal, and a reason there’s an escape hatch. Still, it might cause some confusion.

AFAIK zero-init is unfortunately the default compiler use (but you can change that)

As the only observable behaviour of this_thread::yield is forward progress, because of the as-if rule, the compiler doesn't actually need to replace the loop, when running on a runtime that guarantees preemption. That's the case when std::threads are backed by kernel threads. On a M:N implementation, then yes, a yield would need to be added, but that would be desirable.

Interestingly, posix realtime FIFO scheduling doesn't preempt even on kernel thread based implementations, so one reading of the standard would require yield on this case. But that can actually be potentially catastrophic as FIFO scheduling is expected to be deterministic. But realtime scheduling is already beyond the standard: I doubt gcc and clang will do the transformation by default.

In practice the equivalence is necessary to make some obscure corner of the memory model work and prevent some undesirable optimizations; I expect that in practice the compilers, if they implement this at all, will provide an opt-in flag, but they will optimize as-if the call was there.


> For constructors and destructors it's somewhat unavoidable and not so random, though Rust does better at limiting the blast radius of non-local code, at least in the drop case.

Unlike C++, Rust does not manage exceptions at all; in C++, you must consider situations where exceptions arise.


If panics are set to unwind, you do need to consider it, and the UnwindSafe auto trait is there to help with memory safety, but logical issues can still arise.

It’s way way more rare in Rust though.


There needs to be a way to stop this. A trivial infinite loop can be useful such as for getting you into a state where you can attach a debugger and examine state then have execution resume elsewhere.

Is it hidden if it's explained in the standard?

I think Linus's complain was before there was a c++ standard. An updated version of the complaint would be "this shit is doing too much".


An empty loop, under some non-obvious conditions, on some compiler flags but not others, silently transforms into a system call. In a systems programming language.

I try to minimize use of destructors for the same reason.

Destructors run predictably at list, and are pervasive everywhere. You know that when you exit a scope, be that a function or whatever it may, the destructors of variables in that scope are called. That is clear and consistent. The transformation mentioned above is not.

Understanding the code requires understanding the destructors of the objects you're using. Since they are invisibly inserted, they are a source difficulty in entirely understanding the code.

I do wonder if any of the language servers that insert implied type annotations would ever also show things like destructor calls in a similar manner. It seems like it would be quite useful.

> Since they are invisibly inserted

They're not, all destructors are explicit. Seems like a skill issue on your end.


Since AFAIK I'm still the only person to write a correct C++ (C++98) compiler from preprocessor to object file, I know all about destructors.

Here's a fun one for your amusement:

    foo(a, b, c);
The parameters are pass by value. a, b and c are objects that have destructors. Have a look at the code generated for that.

It is nice that the compiler does the dirty work for you, but the various paths with exceptions and recovery with invisible code may not be well tested.


> Seems like a skill issue on your end.

You're talking to walter bright, the guy who wrote the digital mars C++ compiler


> appeal to authority

Okay.


> Is it hidden if it's explained in the standard?

In the context of that particular complaint, yes. From what I understand the gist of it is basically that you should be able to tell what is going on by looking at the code locally (i.e., the code is "explicit").

> I think Linus's complain was before there was a c++ standard.

These emails [0]? IIRC those are the most well-known ones and they are from the mid-2000s

[0]: https://harmful.cat-v.org/software/c++/linus


I'm sorry Dave, I can't do that, unless you upgrade to a premium enterprise subscription.

Both Monal and Conversations support XEP-0357. Each project runs their own XMPP-to-Apple/Google gateway for push notifications. It has to be that way because the gateway server and client phone application have to be signed by the same developer account.

The client phone app tells your XMPP server which gateway to use (or maybe it's vice-versa, the gateway contacts your server. I forget). In any event XMPP servers like Prosody support this out-of-the-box.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: