Show newer

converting a synchronous API to asynchronous I've replaced all return values with callbacks and all callbacks with return values (technically callback params, but that's how you return something out of sync :V) ... is that a thing? it could be a thing...

@shoom@social.tchncs.de we must secure the means of indirection!

: you may declare a class without defining it, as long as all you're doing is declaring pointers/references to it.
: you may declare a class without defining it, if I feel like it...

@kornel You decided to focus on one particular exaggeration I made and imagined yourself a dichotomy out of that to argue with, while completely ignoring my actual point that I elaborated on twice. Keep not reading what I write, like you don't read the code you review, and instead fish for straw people to argue with, like you fish for unsafe code during code review to headshot your coworkers like it's an FPS.

You don't need to be perfect or never make mistakes to follow basic tool usage guidelines, and rust is not the only language that has a static type checks.

@kornel I know Java is a different language yes, but you are arguing for rust in the same way Java advocates argue about Java. You have some incidental advantages because you took some freedom away from the users and got some short lived hindsight and creative freedoms to optimize your particular implementation to particular programming practices and culture of the day, and then go "haha, gotchya I'm faster". Meanwhile on any language design aspect discussed here you are happy to sacrifice efficiency for you own subjective notion of safety. Total program correctness is safety, everything else is your personal obsession.

I thought you are actually trying to make an argument about UAF across process boundaries, that introduces additional input to the program, but no we are talking about usual program inputs, which case how exactly is that any different from a logic error? Just go back to my original example and imagine the conditional to be an authorization check. "oh no I didn't return after failed authorization check, which caused use after free so I read someone else bank account info instead of yours. Let me just patch this terrible UAF using the power of memory management so something like that never happens. Still not gonna return after a failed authorization check... such security!". You keep repeating over and over again that UAF is exploitable as if I claimed it isn't, and ignoring the fact that it is you who is claiming that logic errors are either not exploitable at all, or somehow less exploitable.

@kornel using a specific API, when we agreed to not use it, and maybe even wrote a wrapper to use instead, is hardly an honest mistake. If you are so rushed that you make such mistakes, I'll be surprised you didn't submit code in a whole another language. "I copied this from SO, it's python, but somehow it still works, lets ship it". I don't know what you are talking about past that point. The only elitism in this thread is rust code reviewer thinking you not worthy to write unsafe code. When I let you write whatever you want and trust you to not write bad code, and during code review genuinely try to understand what you did and why, how the hell is that elitism?

I'll repeat my points one more time:
1. Rust allows you to mechanically identify unsafe code.
2. Rust makes unsafe code (the one that requires extra attention) harder to read.

1. This is only useful to save time for when you don't want review code thoroughly, not when you do. I'm not going to saying the check is not useful when you don't want to do code review. I'm saying it's not useful when you want to do thorough review anyway.
2. This objectively hinders code review.

The usual argument for static checks helping with code reviews is, that they will take care of the easy mechanical stuff while the reviewer will focus on what actually matters to review. Rusts argument is - we will make what actually need to reviewed (according to us) hard to read and easy to ban so that you don't actually need to do code review!

@kornel I spelling out the conclusion from the article you linked, and you are responding to it with "it's fine". Alright it's fine. Java is also faster than c++ cause it can analyze and optimize memory allocation strategy of you spaghetti code at runtime. Sure. Count your blessings. I'm talking about the broader scope and the philosophy of the language.

And yes UAF is exploitable, I didn't say it isn't, but it depends on the program logic, just like the exploit-ability of a logic error. Not to mention that the idea of substituting something for the freed memory is very much specific to modern multi-user system which are notoriously insecure by design. Optimizing mitigation for fundamental flaws of some underlying systems is not exactly a good long term goal for a language, in my opinion.

@kornel I meant that the article is arguing directly against you but you are so entrenched in your misconceptions that you can't see it. Read it again, read the quotes that I pointed out and my comments.

Yes you can't have a borrow checker in c++ across the board, because it goes against the principles of the language, and it's standards of efficiency and ergonomics. Rust did not succeed at the same thing, all it did is abandon the standards of efficiency and ergonomics, just like any other language that came before. I know it has been advertised to you as only leaving the pesky backwards compatibility behind, but that's not the whole story, and anyone who seriously studies both languages knows this.

@kornel I tell you to not use operator * on optional and If I trust you, that is all. If I don't trust you I design a tool that will mechanically 100% guarantee you will not use it, because I can't be bothered to review your code. You appear to think it's difficult to not use an operator when you are told to. Well I guess it is if you are writing code by copy pasting.

@kornel ok so, confirm no respect of coworkers, including no trust for interns to not write unsafe when you told them to not write unsafe. And confirm on making the most important part that you want to review most carefully hardest to read. Excellent argument.

@kornel take your time, read it all, reply whenever, but after reading all plz

@kornel Thank for not reading my point on code review, must be a lot of fun playing code reviews by 360 no-scoping unsafe code.

@kornel Saying where the move occurred would be enough to be precise and helpful. The apologetic part is when it goes ahead and gets insecure about Box not having an implicit copy. Because it knows value semantics are natural and easy to understand, as all literal types in all languages ever use value semantics. And it knows it chose an utterly confusing default with move semantics.
In C++ language default is value semantics, that is exactly the same as literal types, and otherwise you need to be explicit. Const reference semantics is conventional default (that is evey c++ programmer is taught to always accept function parameters as const&, unless they have a reason to not do that). Most other languages also prefer that combination (value and const ref), but decide implicitly which to use based on types. This implicit choice that is different based on type is a common source of confusion in pretty much all garbage collected languages.
Rust on the other hand, not only makes the same sort of confusing implicit decision, but also chooses the most confusing default - move semantics (for the sake of performance no less), I presume because it just can't wait to show off the error messages.

Regarding your code example. How is comparing two rows of an image a logic error? You clearly have a memory management error. The embarrassing part is that, as you say, it's an intentional hack and, with the amount of tools rust gives you to get things right, it probably took more effort than a proper solution would. Just based on the code you provided and the premise of lazy evaluation, get_row must return an object that has shared ownership of the underlying resource and unique ownership of a buffer that it will use. If that's not easy to do in rust, I don't even know what's the point of the language anymore.

Again this is clearly a memory management issue with a memory management solution. I see no logic errors in what you presented unless you want to imply that the entire image type and its interfaces are one giant logic error, just because you couldn't implement proper memory management for it. I wonder if this is how C programmers end up not being able to tell sizes of files. "Ah, that is clearly a logical impossibility, cause I can't do that easily in this handicap of a language". If the underlying resource can't be shared, or can't provide random access to rows (even if lazy evaluated), then your image should be called image_stream or something, and get_row should be called skip_rows, and rust will compiler will not teach such things.

@kornel Yes that's how static guarantees work, everyone knows that. C++ provides the same exact static guarantees for variant, using std::visit, and that seems to be your complaint - that visit doesn't work with optional, but you don't know enough c++ to word it correctly. That is hardly a fundamental philosophical conflict, it's simply an omission, nothing stops visit from being overloaded for optional or even raw pointers if you want the same guarantees with those, and regarding the efficiency of unsafe operator * you are again missing a point. Static grantee has a cost of a null check in some situations where compiler can't proove them to be unnecessary. This is not freaking javascript - I'm not afraid of a null check (or omg a function call overhead) in a getter function to paranoically create a local every time I need to refer to a field/property. it's about situations where were a single top level null check is completely unnecessary and the compiler can't prove it. That's when you use operator * without a explicit check, that safe interfaces like value(), value_or() or equivalent of std::visit dictate. Especially when you write a generic code that you expect everyone to use to make the language more coherent. Again nobody should have an excuse to not use it, and even small inefficiency is an excuse for someone, cause it would be in their hot path.

Let's now deconstruct the justification you present for the butt ugly unsafe optional access in rust that you so kindly demonstrated.
>It is important that it stands out in code reviews.
Why? I see two possible reasons here:
1. To slap that pesky intern on the wrist for trying to use "unsafe" code without first earning the right, which is reserved for the elite class of programmers only (as in c++ programmers)
2. You respect your coworkers decision, but want to pay extra attention to this clearly necessary, but complicated and hard to reason about code.

If it's 1, then congratulations you will get a lot of support form corporate executives who who treat their workforce as commodity.
If it's 2, then why in the world do you make the supposedly most crucial part of your program to get right, unnecessarily verbose, hard to read and write. It's like "ah yes I magically have more time to review this, now that unsafe keyword is used". No you don't, the harder it is to reason about the more expressive and simple it needs to be. Everyone who spent any amount of time with C or C++ knows what * and -> are and how they are not safe. They even might now of some common patterns and anti-patterns. These should not be used often in modern c++ code and they would definitely be noticed in a proper code review, even is relatively old code based. "Oh no but I might not actually notice the tiny little star, and I need to be able to grep for unsafe, cause I'm not actually reviewing anything, I'm just looking for excuses to bash the intern". A single character difference that changes a reference to a pointer is notices in c++ code review and discussed in length, if it's a real code review, because you are looking for logical patterns using your brain, it's not an action game with a bunch stars wheezing around the screen for you to catch.

Finally I thought you are full on tangent mode with the optional stuff, but now you want to imply it's UAF? UAF that rust handles that c++ can't is a completely different thing that is about resource management. And did you even read the google doc you are linking to? It almost repeats after me and you think it's an argument in your favor in this discussion? I guess it's not a mere bubble it's an explosively hydroformed steel sphere:
yewtu.be/watch?v=Sk9WyEfzWPg

Literally the points in the article.
1. buffer overrun runtime checks is a non issue, as in you don't need to change the language spec to deploy them wherever you want however you want. It's a runtime thing either way.
2. UAF, UAM type problems can be solved through use of better abstractions with clear invariants that are easy to adhere to.
"We believe that, given sufficient tolerance for micro-efficiency regressions, we could essentially eliminate spatial unsafety in C++ in first-party code. We could do this (and have started doing so) with a combination of library changes and additions, compiler options, and policies/style rules and presubmit checks (including banned and encouraged classes and constructs)." As in - you do not need a borrow checker that prevents you from properly using a linked list in order to write resource safe code.

"Micro-efficient and ergonomic temporal safety remains an open problem" as in rust is not efficient and ergonomic, by the standards of c++.

Nothing in the article suggest that UAF is always exploitable, or that it is worse than a logic error in your program. You are the only one trying to argue that, repeatedly asserting without explanation and presenting completely unrelated examples and arguments about general usability and expressiveness of the languages, which lead me to conclude you have no actual argument with regards to the OP left.

@kornel It's also funny that the compiler chooses to explain default move semantics in such an apologetic fashion. If it's so unnatural, that you need to explain in great detail, why make it default? In c++ the move would have been explicit, no need to explain, it's there, someone wrote it so they obviously didn't want the value to be used from that point on, there is no question whether they wanted a copy or a shared state or reference semantics.

@kornel this is beside the main point, but you seem to imply here that rust will suggest an early return in my example, which would be absolutely bonkers, so I decided to try it out

ix.io/3JpI/rust

the early return might be obvious to you because I told you it's an error condition, but otherwise I don't think it's obvious at all.

@kornel I'm not saying you should ship the debug libraries I linked, I just wanted to show that as proof of concept alternative implementation of standard containers. You can have much simpler implementations that only do bounds checking, and those probably exists, I just never needed anything like that outside of debug builds, so I don't know of any other examples. The point being that this risky default is an implementation detail and not core issue of the language. You do not need a different language to fix this particular risky default, if fixing it is proven to be important for your use case.

So what I'm asking for is an example of logic error that causes a memory error that rust can identify statically, for which in context of a "security-focused project" a fix of the memory error that does not address the logic error will be strictly better than not fixing anything at all. Quite the mouthful, but I'm just repeating the context here to be clear.

Can't think of anything like that with optional, but I'll rant about it anyway, cause it's interesting, and an easy example to explain what you call "risky defaults". You write "in the name of performance" as if that's not one of the core principles of the language that it promises to its entire user base. The standard library APIs can't be inefficient. The implementation can be, you can beat either libstdc++ or libc++ or whatever microsoft has (especially whatever microsoft has) for you own purposes, but reusing the same API. There should be nothing in the API that dictates an inefficient implementation or use, because if there is such a thing, if there is a more efficient API, the c++ user base will implement it and use it. The end result is everyone has their own incompatible versions of one or the other type or function.

std::optional is designed in context of c++ to replace a common c++ pattern
struct {
T value;
bool exists;
};
(often abbreviated as std::pair<bool, T>, cause who wants to write a struct?)
with an invariant that the value is initialized if the exists is true. It's not meat to replicate whatever hip programming style, though that might happen in time. The primary purpose is to ensure that nobody has any good excuse to write any version of that struct ever again anywhere - safe code, unsafe code, high performance code, small size code, CPU, GPU, microcontroller, super computer, FPGA or what have you - no excuses, use optional. You need something safer - wrap optional, never write that freakin struct, unless you are implementing an API and preferably ABI compatible replacement for std::optional. The major goal of the standard library is to be a common ground for all codebases, to make code more expressive and readable and minimize dialects. Not that c++ doesn't have dialects galore, but that's not by design, in contrast to rust which requires a dialect by design.

I guess what you expected instead is a static guarantee that the not-exists case will be handled. That's conceptually closer to a tagged union
struct {
union {
T value;
std::monostate no_value;
};
enum tag index;
}
The equivalent of which would be std::variant<std::monostate, T>, which can provide the static guarantee as long as you stick to std::visit, but again its main goal still is to be a replacement of all uses of the tagged union in all contexts, not directly matching patterns (hehe) from other languages. Now variant incidentally fails at this
stackoverflow.com/questions/48
encouraging people to use workarounds or implement their own tagged unions. This must be a great boon to you, as c++ failure is essential for you to justify rust. The OP is pretty much saying "stop dunking on c++" (albeit in provocative fashion) and you and another fan here jump in with "nooo, I must continue dunking on c++, cause otherwise my bubble is gonna burst". But if you are going to do that at least understand the goals of the language you are dunking on, which is to be expressive and coherent across the board, to make the logical invariants as clear as possible and consistent in all code, and make sure that as long as you diligently follow them, the program will be correct and performant in every way, including (but not limited to) resource management. And if you can not follow the logical invariants (not because your indisputable stupidity and lazyness, but because they are not expressed clearly enough), no amount of guaranteed resource safety (in a subset of your language) is going to save your from the logic errors, that you are now trying to argue are objectively better than memory errors for "security", because you just can't let it go.

@bonifartius sans any anti-capitalistic wishful thinking about our bright futures, is "artists gotta eat" a actual practical argument for copyright in the grim reality that we currently exist in? Anyone know any financially struggling artist that pay their bills with copyright "strikes"? The artists I personally know to exists fall into 3 categories (not mutually exclusive):
1. digital artist working full time or as a contractor, who forfeits all their IP to their employer.
2. traditional (say painter or sculptor) artist, who sells actual physical works of art, for high prices to niche audience who appreciate the works subjectively and often specifically because they come from the artist.
3. digital or traditional artist, who sells physical copies (say prints or moldings) of their works for low prices to niche audience who wish to support the artist.

I have a feeling that if you are banking on copyright, then you are either succeed and get rich, or fail and get nothing. That's the nature of copyright. You can't make an "honest living to just pay your bills" on copyright.

@kornel it's not that hard for a language to be more expressive than C. I'm glad you upgraded.

@kornel rather unfortunate that you chose a buffer overrun as an example, as rust provides no static guarantees and only runtime guarantees for that. An equivalent can be achieved with standard library implementation in c++. Here is a proof of concept: gcc.gnu.org/onlinedocs/libstdc
It's not exactly meant for production so it does way more than just bound checking, but a similar thing could be tuned to whatever needs. It's also ABI compatible, so you don't have to re-implement the whole worlds or revert back to C level for inter-op with the existing unsafe implementations. It makes no sense to introduce this runtime overhead across the board as opposed to opt-in, because it goes againt one of the core principles of the language, which rust often also is marketed to adhere to, until someone points out that it doesn't in this specific case and make the salesmen cry.

If you have any other examples I'm interested, since otherwise I have no respect for any sort of common industry practices, and whatever backwards reasoning they follow, especially with regards to security.

Show older
Qoto Mastodon

QOTO: Question Others to Teach Ourselves
An inclusive, Academic Freedom, instance
All cultures welcome.
Hate speech and harassment strictly forbidden.