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

I'm not convinced this is true. Otherwise, it does not bode well for Rust code because any type safety glitch will be considered a vulnerability. This would be really challenging for Rust developers because it goes beyond unsafe Rust code. You can easily have unexpected panics because your types do not enforce invariants as you expect. If a library has such a bug, is this a denial-of-service vulnerability in the library? Rather than dealing in absolutes, I would say that it's impossible to tell in isolation. If applications do not misuse the library in ways that triggers the panic, probably not, and treating this as a vulnerability just results in pointless noise.

Determining the impact of library bugs can be really hard. For example, some functionality might be so broken that it's simply impossible to use correctly, so a buffer overflow on top does not make a difference. Or a buffer overflow vulnerability triggers consistently during system boot, so that you never get to the login prompt. These can hardly be considered vulnerabilities. On the other hand, we have clear buffer management bugs, where we must expect that there is application code out there which specifies tight buffer bounds and requires that the library stays within that buffer. (Rust should help here, at least out-of-bounds accesses should turn into panics.) But most bugs are unfortunately somewhere in the middle. Tools like Debian Code Search can provide some evidence. But in the end, it's more subjective than I'd like it to be.

On the extreme end, we have compiler soundness bugs. I'm a bit of worried that I'm hitting any of those when I'm tweaking the types until the compiler no longer complains. Beyond the basics, I really don't have a grasp of Rust's type system rules. But I suspect they very difficult to hit by accident, and even if I do, the code must be miscompiled in meaningful, but difficult-to-notice way. All that seems rather unlikely, which is why these bugs aren't treated as vulnerabilities.

I really think we need some corrective factor (such as potential implication impact) when determining whether toolchain bugs are vulnerabilities.


You said

> I'm not convinced this is true.

But it is. It is true that Rust libraries could take this position of "any API misuse causing vulnerability is a CVE" more to the extreme, currently it is applied to memory safety but it could be applied to panics as well. However it is still true that pretty much all Rust libraries treat API misuses that cause UB as a CVE, and pretty much no C/C++ library does that, and that inflates the number of Rust CVEs.

> On the extreme end, we have compiler soundness bugs. I'm a bit of worried that I'm hitting any of those when I'm tweaking the types until the compiler no longer complains. Beyond the basics, I really don't have a grasp of Rust's type system rules. But I suspect they very difficult to hit by accident, and even if I do, the code must be miscompiled in meaningful, but difficult-to-notice way. All that seems rather unlikely, which is why these bugs aren't treated as vulnerabilities.

Rest assured that you are much more likely to hit a miscompilation in your compiler's backend, and that it is much harder to detect.


> Rest assured that you are much more likely to hit a miscompilation in your compiler's backend, and that it is much harder to detect.

The LLVM provenance bug is a really nice example. The Rust which tickles this bug (LLVM emits nonsense, claiming that two integers a and b are different but then calculating that a - b == 0...) is fairly clear, you wouldn't write it by accident but it's obvious what it should do, and unsettling to discover that the bug isn't in Rust's compiler frontend but in LLVM.

You can write equivalent C or C++ to show the bug with Clang - but when you try to write it you'll struggle, not to reproduce the bug per se, but to stop writing Undefined Behaviour, which invalidates your bug report because the LLVM devs will say "This is UB, working as intended". The non-UB reproducers are much more elaborate than the safe Rust was.


What is the "LLVM provenance bug"?

https://github.com/llvm/llvm-project/issues/45725

Are you familiar with pointer provenance ? This gets very deep, very fast, so if you don't know and don't want to know I can't help you, but if you do want to know try maybe: https://www.ralfj.de/blog/2018/07/24/pointers-and-bytes.html

So e.g. if we make a pointer to thing A, which we then destroy, and then for a pointer to thing B, and then we compare these pointers, even if the address which will be the only bits making up this pointer in hardware was identical, a language could choose to say these pointers are not the same (Rust says they compare equal but that's up to Rust). LLVM is equipped to do this optimisation. So far, not a bug, though perhaps not what you expected...

However everybody is pretty sure that we don't want provenance for other value types. It's troublesome for pointers but we're used to it and it unlocks important optimisations, but for every other value type it's just extra trouble. So Rust's provenance model says only pointers have provenance, and proposals for C and C++ likewise. If we ask for the address from a pointer, making it into just an integer, it should not longer have provenance.

But, LLVM doesn't really track whether a value "is" pointer or not per se, so it ends up applying that "they're not equal" optimisation to the integers we've made from pointers, even though the integers are definitely equal and we're about to do a subtraction to prove it. Bang.


>Otherwise, it does not bode well for Rust code because any type safety glitch will be considered a vulnerability.

I mean, this is basically true. And it goes beyond type safety - there have been CVEs filed against the Rust stdlib for TOCTOU problems of a kind that the C++ stdlib is absolutely replete with (often the exact same ones in the exact same places, to the extent that comparable APIs exist) which ended up being fixed quickly in Rust and largely ignored in C++, if anyone bothered to file in the first place.

For sure does create headaches for those who need to categorize CVEs by impact, but on balance I don't think it's a bad thing for the ecosystem. Creating a culture that wants to fix soundness issues rather than mark them as WONTFIX with a line of documentation is a core principle and value proposition of Rust in the first place.

Quoting https://cor3ntin.github.io/posts/safety/

> But the borrow checker is not what makes Rust safe. Rust is safe because it decides to put correctness first by default.

> Rust is safe by culture.

Better to pay a penny to fix it today than a pound to deal with the fallout down the line.


The glibc test suite contains a few tiny FUSE file systems. For example, there is one that just happens to contain every file that mkstemp attempts to create, for a test that exercises the O_CREAT|O_EXCL failure path. Like the Rust fuser crate, it uses the kernel API directly. The tests are slightly brittle because sometimes we encounter a new LSM that triggers unexpected file system operations, but it's not too bad overall.

What I found funny when I discovered it is that you can create a thread in the same process that provides the FUSE file system implementation for this very process. It makes it much easier to write certain tests, especially debugging. We had to teach valgrind that more system calls effectively perform callbacks into the same process, but fortunately valgrind already had a FUSE_COMPATIBLE_MAY_BLOCK mechanism for that.


Indeed, the glibc test suite .. plus a suite of BPF scripts for instrumenting .. can provide a great deal of information about how things are running in a target OS .. if you give a process its own filesystem and then rig up a bpf console with some hot gnuplot, you can get some very significant details, at the i/o level, about the heuristics of an application, library, user-space module/plugin, etc...

It used to be you had to wade through massive logs though, if you don't get things quite tweaked - but in the AI/ML agent sense of things these days, just describe what you need, FUSE the right nodes, and run the bpf scripts, yo ..


It's likely this is a hypervisor misconfiguration. Either way, one has to wonder what kind of mitigations for cross-tenant leakage they are missing.

Or on purpose, because the CPUs with AVX are more expensive. Or historical: the hardware for this kind of service may have been old, and you can't tell people that if you buy today you get a processor with AVX, but tomorrow you may get one without. I haven't checked if they upgraded their low-cost options in a while.

You can try to execute POPCNT. If it does not fault, its presence is only hidden via CPUID.

Live migration support may be the reason why they stick to the baseline. That's most likely to be migratable across different CPU types. Although with a bit of effort, you can figure out what is support by your fleet and configure that into the hypervisors.

I'm skeptical that pre-SSE-4.2 etc. CPUs are economically viable for running customer workloads due to electricity costs.


Either it's a misconfiguration, or it's intentional (only providing a "bare-bones" machine for the lowest price level, even if the underlying hardware would support more)?

Isn't there a thermal cost to AVX instructions? Or, thinking of other reasons, if you're splitting up physical hardware into a "vCPU", it it possible that AVX doesn't map cleanly?

POPCNT is an interesting example. Runtime dispatch (with a conditional branch) would actually make sense for it because it's comparatively difficult to implement from scratch. PDEP and PEXT might be similar (but I don't think compilers pattern-match for it, unlike POPCNT). AArch64 uses localized run-time dispatch extensively because LL/SC atomics are so very bad on current cores, but there isn't anything comparable in the x86-64 space (POPCNT isn't that frequent).

For many other things, like using a YMM register to copy a 32-byte struct or a variable shift, run-time dispatch just not make sense. You will only see a benefit if you generate this code unconditionally. For FMA, you wouldn't even get bit-identical output, leading to testing concerns.


Newer (relatively speaking) x86-64 instruction sets support many three-operand instructions, which are actually easier to use for compilers than instructions with overwritten source operands or hard register constraints. Pattern matching for instructions that do not have a direct C representation (such as NAND) is also pretty standard in compilers. Auto-vectorization is more tricky (especially when you want code to actually run faster …), but some of the new ISAs are impactful without it. And of course there are expanders for fixed-size memcpy and memset that can use wider vector instructions quite easily. Those operations are quite common.

Do SAT scores measure anything about pedagogic aptitude? I expect that at best, you get some form of correlation (in which direction?).

For teachers, other things matter more than reasoning skills or subject matter knowledge, especially in rural or otherwise challenging communities.


It shows revealed preference: all smart people decide to work anywhere but education and public schools are scraping the barrel for talent.

Results you can see with your own eyes. USA had to significantly dumb down SAT, switch to dumbed down “Common Core” curriculum, ditch gifted&talented programs across the board, ditch SAT requirements for college and introduce remedial math at Harvard!!! The creme de la creme of US Education system.

If American teachers were any good, private schools would not be able to charge more than Ivy League tuition for very simple secondary education

This is all consequence of what kind of people decide to become Teachers, and what kind of people decide to become Wall St Traders

In the end this shows up in massive dependence on Foreign talent via H1B visas. US is importing engineers precisely because Americans did not have good STEM teachers who could teach them math and logic in middle school.

https://abcnews.com/amp/GMA/Living/us-students-reading-math-...

https://www.realcleareducation.com/2025/03/20/harvard_launch...


The article mentions Stripe's product in this space: https://stripe.com/en-us/radar

There are similar offerings from other companies. I don't know if bundling this with payment processing is common.


U.S. chargeback rules are different. In other countries, you cannot repudiate credit card transactions that you authorized (and this applies to Mastercard/Visa, too). You need to do something else if you end up in a dispute with the merchant.


You open a ticket where you describe what happened and attach everything you have. It happened to me twice over the years, both with Visa, and I had them both approved. I'm not sure that in the age of AI agents they would care anymore, but I can dream right.


Over here, it's common that consumers don't have a direct relationship with the card company. I'm not sure if they would even be able to identify me.


What do you mean by card company?

The cardholder’s contractual relationship is always with the card issuer, which is usually a bank or some other financial institution. This is no different in the US. If something on your bill seems off, you contact the one that issued it, i.e. your bank.


Hmm nevertheless my cases were handled by Viseca, not by my issuing bank. I don't know why, is it because of my bank, or my country, but yeah it seems to be different.


Banks can (and often do) outsource chargebacks to their processor or another third party, but never the card network (since that’ll be the entity ruling on the case in the very unlikely case it goes into arbitration).

Viseca seems like it might actually be an issuer directly (it’s also a common model that banks only act as program managers, delegating actual issuance to a different entity) but I’m not familiar with them.


That’s completely false. Visa/Mastercard chargeback rules are fairly uniform globally, and disputes are possible in many (if not all) non-US countries as well.

Whether your bank knows how to use them well to represent your interests is a different matter. For example, I’ve seen banks decline chargebacks against bankrupt merchants in certain countries because they were poorly advised about the legal ramifications, and other banks in the same country win the exact same kind of dispute. Lacking sufficient reading comprehension to parse the dispute rules (it’s a long PDF!) also seems common.


Surely you need an alternative to Box<dyn Error> for reporting memory allocation failures?!


Anything other than panic/abort on allocation failure is outside the scope of the vast majority of programs, including anything using the standard library in Rust. I wouldn't worry about Box<dyn Error>.


You're already writing Rust in a very different style if you're writing the type of code that gracefully handles allocation failure. It's to Rust's immense credit that this type of coding is actually fairly well-supported (unlike in Go), but you're already a bit off the beaten path for stuff like error handling.


Not sure what your problem is?

If you need to handle an allocation error in the error path, then the error reporting path must abort, which means that the allocation error must be bubbled up.

There is no real solution to an allocation error inside the error path. Even if you preallocate an arena for errors, the error might be large enough that it won't fit inside the arena.

Hence the best thing you can do from that point onwards is to have an error enum with an AllocError variant that doesn't allocate. Said error won't contain any information beyond line numbers of the allocation error since you just don't have the space for it.

In the end you will basically end up with panic free code, but the error still bubbles up like regular unwinding.

So yeah you can do it, and I will do it in the future, but I personally think that the people who think this is some huge deal breaker don't understand the problem in the first place.


A &(dyn Error + 'static) should be fine for that; you don't need any allocated/variable sized data in a memory allocation failure.


stacktraces? might also be useful to know whether or not the latest allocand was a jumbo sized allocand that caused the failure?


Do you really want that data passed back down to the caller of the allocation? From the description of the failure state you'd want to log that data instead: what's the caller of the allocation going to do if you tell it it failed with a crazy size? It already knows the size, it's the one who asked for it.


So, suppose it's a rust library -- you're locking me into whatever logging system the library author chooses? Maybe I'd like to consume the relevant data at the entry point and send it to a logging system of my choice.


A Rust library likely wouldn't be returning an opaque Box<dyn Error> to begin with. Errors are part of a library's API—it's what allows consumers to handle them—so you'd define an enum of possible errors your library could produce and return that, which would be stored on the stack.


What about the data in the error payload?


You can do better than the errors in other languages. You can provide all the relevant information in the emum variant.

  enum MyApiBindingCrateError {
    // You didn't provide an 
    // API key. Maybe we should 
    // design our interface to
    // make this impossible 
    ApiKeyMissing,

    // Client was unauthorized 
    // to make this request 
    AuthorizationError,

    // The entity you requested 
    // did not exist (404'd)
    NotFoundError,

    // You're sending too many 
    // requests to the server 
    TooManyRequests,

    // That specific error with
    // the API
    // Maybe users can't delete 
    // folders until they're empty 
    // Whatever 
    SpecificApiIssue1,

    // Some other specific error
    // with the API
    SpecificApiError2,
  
    // Server didn't respond the
    // way we expected. 
    // Here's what it told us
    UnexpectedHttpResponse { 
      // HTTP status code
      status_code: StatusCode, 

      // If it had a 
      // string-encoded body
      body: Option<String>,
    },

    // Unhandled Issue with IO 
    IoError(io::Error),
  
    // Unhandled Issue with 
    // request library
    ReqwestError(reqwest::Error),

  }
The beauty with Rust is that you can create really detailed concrete errors at the crate level. Your callers will know exactly what the actual error states are.

Your application can be a little less structured if you want. Though with LLMs, I'm using anyhow and thiserror a lot less.


In the current context with regards to failed allocations, you're also supposed to add a variant that wraps AllocError.


I think this is a clash of terminology: a Rust enum isn't an integer with pretensions of an identity.

You'd describe it as a tagged union in some languages. So when you say you'd return an error with extra information, what that information is is associated with the specific variant of the enum.

Using yuriks AllocError as an example, if the error is SizeTooLarge, it has the size field. Other errors may have no additional data, others may have different data.

When you return an error from your allocating function, it's a known size, the size of the largest enum variant + the discriminant (tag).


I'm aware that a rust enum isn't just an integer? How would it have a payload if it were just an integer?

right. If allocerror only has size field, where do you stash the unwind information (which could be of arbitrary size)


There's a few confusing things here. For one, just because the allocator gave you an AllocError, that doesn't mean your function has to return an AllocError: you can return whatever error type you choose. If you want to collect a stack trace at that point, put one in there.

What value would a stack trace that includes internal allocator functions be to you? What do you lose by having to collect the stack trace at the point where your function receives an AllocError?


That's part of the error enum.

  enum AllocError {
    SizeTooLarge { size: usize },
    // etc.
  }
This enum has a known size and doesn't require any dynamic allocations.


usually “stdout” is good enough, wrapper/runner routes output to logserver for collation and search. who cares about formats as long as it’s reasonably structured and searchable?


it depends, if the functionality represented by the library is known to require a lot of memory (or simply allocation failures are an expected part of its operation), then it should be pretty much part of the API, probably with some tracing/diagnostics interface to get the required visibility into how much memory goes and where.

but for most libraries I on allocation failure I don't expect any fancy logging system. maybe even panic is fine.


And how do you store a stacktrace without allocating?


If you let the allocation error panic you will get your stack trace.

You can't have a stack trace on an error in the error path that failed to allocate. If you have a "jumbo sized" error and the error fails to allocate, it won't get reported. The only reporting you will get is that the error failed to allocate and this new allocation error overrides the error that failed to allocate.


if you have an OOM and want to log why, if your logging allocates it will likely fail as well. You could in principle work around this with enough effort, but "properly" handling OOM is typically much more trouble than its worth.


Any time I mention "but I would like stacktraces with my errors" I get told I'm doing it wrong.


That's because the types of errors where you want a stack trace are a relatively small subset of all possible errors.

Stack traces are only useful for errors that indicate a bug in the program, i.e. something a programmers has to respond to. It's not useful for the vast class of bugs that are a result of wrong input, wrong external state, or infrastructure issues.

Rust projects tend to favor panicking over error handling for programmer bugs (which does indeed give you a stack trace depending on environment variables), or even better encoding the invariants in the type system, but there are cases where an error coming from a library are truly, actually unexpected, so both `anyhow` and `thiserror` do provide support for attaching a stack trace in those situations.


See? You get people explaining to you that you actually don't want a stack trace because xyz.


This sounds disingenuous. They explained why the language doesn't force stack traces on all errors, and then explained how to get them if you want them.


I see a very opinionated explanation, not a "this is why the language does not" explanation.

>Stack traces are only useful for errors that indicate a bug in the program, i.e. something a programmers has to respond to. It's not useful for the vast class of bugs that are a result of wrong input, wrong external state, or infrastructure issues.

This is a personal opinion, not something you can declare as the objective truth. There is a lot of value in seeing what path the program took before it encountered a eg. validation error.

>but there are cases where an error coming from a library are truly, actually unexpected, so both `anyhow` and `thiserror` do provide support for attaching a stack trace in those situations.

This is wrong because it's up to the library to attach the stacktrace, not the userland code using the library, so saying "you can get them if you want them" is not true. If the author of the library did not decide to attach the stacktrace, your only option is wrapping it yourself, which you can only do if you already know up front all the paths that can fail. Also, you are not supposed to expose errors from a library with anyhow, they are only for application/top level code.


I'm curious, what's the value of a stack trace of another person's library functions? As mentioned, you can get a stack trace that includes all of your code, that's what was offered to you.

The only thing a library gathering a stack trace instead of you gives you is that it includes traces through code you didn't write & ostensibly aren't responsible for. If you're going to go to the effort of tracing through a dependencies code, you might as well add the stack trace yourself; it's a single line of code from the standard library to collect it, std::backtrace::Backtrace::capture().

EDIT: capture will only actually grab a trace when env vars say it should, you can use force_capture to ignore those. To get to why this isn't the default for errors you're asking for, here's a line from their documentation:

> Capturing a backtrace can be both memory intensive and slow


Ideally (in my ideal world), it would be Result<T, E> that holds the backtrace. The value is that I don't know up front which method call is going to cause an error that is hard to track down, which is why I don't see how "instrument your calls with backtrace yourself" helps. It requires that I already have some idea about the execution path, otherwise I don't know where to put the backtrace instrumentation.

Since Backtrace::capture() is already tied to an env var, we could have the backtrace on Result without affecting performance, since you would only enable it for debugging. This would allow you to eg. easily track down a situation where you see in your prod logs that you are encountering a lot of "validation error: string is too long" but you can't tell where it is coming from. Flip the env var, redeploy the application, read the backtrace, turn off the env var, fix the problem.


> track down a situation where you see in your prod logs that you are encountering a lot of "validation error: string is too long" but you can't tell where it is coming from.

Capturing a stack trace is a hefty operation: making it happen on _every_ error creation, which would include creating an error in response to another error (like <failure to allocate> causing <failure to create object>) could easily grind a production server to a halt. Especially if there's correctly handled errors happening: every one of them will pay this cost, every time.

It sounds like a really specific problem here; the log line that's happening is generic enough that it doesn't identify which line of code is emitting the log, so you can't just add `capture` to that line (what logging system even does this? printf logging?).


I feel like we are talking past each other, because you ignored the whole part about "it is already tied to an env var, and it would be still tied to an env var" that you would only enable on demand, so who cares if it's a hefty operation? Also what about other languages that capture stacktraces all the time with exceptions, or scripting languages with type errors, where you can't even turn it off? Rust is somehow different?

It is a specific problem, so what? You see that you are sending 500 from an axum handler, and you are logging "serde deserialization error: line 4 invalid", wouldn't it be nice to see where that came from, without instrumenting all the places you are deserializing something?


Rust errors are not exceptions. Catching exceptions is unbelievably expensive in all languages that support them, compared to handling a Rust error value.

Some languages have exceptions as the only error handling mechanism (C#, Java, scripting languages), and it sounds like that's what you're used to. But this is also broadly agreed to be a severely limiting factor of those languages, resulting from being designed at a time when we didn't know better.

If you want to go fast (and Rust does), you cannot be catching exceptions in the hot path, and you certainly can't be throwing exceptions that carry stack traces, because walking the stack to build up the stack trace is many orders of magnitude slower than returning an error value.

Rust's error handling modes are designed with the benefit of hindsight from all those other languages from the last few decades, and reflects the fact that errors broadly fall in two categories: validation failures and programmer errors. The former should be a cheap error code that can be handled, the latter should terminate the program/thread/task and give you enough information to diagnose the problem.


I can't reconcile what you're asking for with the situation you're describing. If every single error everywhere in the program created a stack trace and logged it at creation time, your error would be lost under an avalanche of benign errors that are handled. And if you only want to selectively log _that_ error that's interesting, you need to selectively modify the place that logs it, which you don't want to do (because you don't want to have to find it).

It sounds like what you want is the errors you log to always log stack traces. Which is a fine position, I do something like that. It's just not something that can be the default, because it can't be done everywhere.


No you just wrap it the way https://news.ycombinator.com/item?id=48267094 showed.

Obviously you need to explicitly include the line numbers since the error reporting Rust is closer to C (errno) than Java or JavaScript.

https://users.rust-lang.org/t/getting-line-numbers-with-as-i...

Just do what feels right. I personally am a big fan of Java stack traces.


The comparison here is to Go, which doesn't have any way to handle memory allocation failure AFAIK.

In Rust you can do that (mostly on nightly although not only), and yes there are alternatives for this case, but it's rare anyway.


Do you have a public reference for the “all future Intel CPUs” aspect? The AVX10 change (no more 256-bit-only EVEX tier) is well-documented in compiler patches and whatnot, but what I haven't seen so far is an unambiguous commitment that starting with 2027 (say), all new CPU models will support AVX10.

For example, Intel stated this:

> Intel® Advanced Vector Extensions 10 (Intel® AVX10) introduces a modern vector Instruction Set Architecture (ISA) that will be supported across future Intel® processors.

They don't actually say “all”, and it is probably meant to apply to future microarchitectures anyway. Depending on various factors, Intel may end up designing new CPUs based on existing microarchitectures well into the 2030s.


Intel® Advanced Vector Extensions 10.2 Architecture Specification, Revision 5.0

Page 18:

> 3.1 INTEL® AVX10 INTRODUCTION

> ...

> This ISA will be supported on all future processors, including Performance cores (P-cores) and Efficient cores (E-cores).

As you see, now they actually say "all future".

The Intel Nova Lake desktop and laptop CPUs and the Diamond Rapids server CPUs will mark a jump in the Intel ISA, by changing the CPUID CPU family number for the first time after a few decades and by introducing not only AVX-512 across all cores, to match AMD, but also the APX ISA extension, which adds features that remove some of the advantages of Arm Aarch64, by increasing to 32 the number of general-purpose registers and by adding double-register load/store instructions.


It's confusing because this statement predates the release of Panther Lake and Amston Lake. Neither support AVX10.

I'm excited about Nova Lake as well. Maybe not so much for the EGPRs (maybe we should have made 4 of the new registers callee-saved?), but there are other goodies as well.


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

Search: