Puffs: Parsing Untrusted File Formats Safely
github.com
github.com
• All operators have equal precedence, so parentheses are required when mixing operators. I guess they’ve seen one too many bugs from precedence confusion.
• It doesn’t have dependent types; rather, it uses a proof checker with some built-in knowledge about the language, and a way to specify preconditions, postconditions, and invariants that the checker can reason about.
• There is limited effect typing: functions can be marked pure, impure (!), or impure coroutine (?).
• There doesn’t appear to be any kind of polymorphism—over types, effects, refinements, or proofs.
Just to play devil's advocate (aka be annoying); parentheses are operators.
Ref: https://softwareengineering.stackexchange.com/a/208354/69247
WP describes: https://en.wikipedia.org/wiki/Operator_(computer_programming...
> Occasionally parts of a language may be described as "matchfix" or "circumfix" operators, either to simplify the language's description or implementation.
Bu then, the term "operator" seems to also have mathematical roots: https://en.wikipedia.org/wiki/Operator_(mathematics)
The relevant part seems to be the definition refering to syntax, or denotation:
> Operator is also used for denoting the symbol of a mathematical operation. This is related with the meaning of "operator" in computer programming
Now, whether "the symbol" implies a singular symbol, and excludes delimiter symbols might be a matter of semantics ;-)
It comes from canonization process in Vatican where someone (who is not an actual satan worshiper or against the church) argued in favor of the devil (against the candidate) to force the other pro-sainthood side to provide good solid proof of the sainthood, miracles done, etc. of the candidate.
What you're doing is just being annoying by nitpicking the casual wording. There is no debate to be had from "parentheses are kinda operators" and the meaning of the original line was understandable, even if you consider parentheses operators.
Doesn't that mean it rejects precedence? In a language equal precedence (e.g. Smalltalk), operators would just be executed from LTR or RTL.
That… is true, and I have a hard time computing how it would even work.
2-3^4 [1, 2] ⧺ [3, 4] ⧺ [5, 6] × 2 × 3
(([1, 2] ⧺ ([3, 4] ⧺ [5, 6])) × 2) × 3
[ [ [1…6], [1…6] ],
[ [1…6], [1…6] ],
[ [1…6], [1…6] ] ]
I dunno how good an idea that would be in practice, though.See e.g the "precedence bug" comment I wrote elsewhere on this page.
Yep. I just filed a precedence bug at https://github.com/jbangert/nail/issues/7 for Nail, which is a security-concious project. (It was presented at LangSec 2014).
> It doesn’t have dependent types.
Yep. Programming language theorists love type systems, and dependent types are one way to prove bounds safety, but they're not the only way. Puffs is an imperative language, not functional (for the reasons described in https://github.com/google/puffs/blob/master/doc/related-work...). With mutable state, a Puffs variable's within-bounds-ness can change over that variable's lifetime, but its type does not.
> There is limited effect typing: functions can be marked pure, impure (!), or impure coroutine (?).
Yep. That's the current design, although I'll probably revise that based on recent experience. The question mark denotes a coroutine, as you noted, but also whether that function can (very roughly speaking) throw an exception. I think we will need separate syntaxes for those two concepts, since I've wanted the latter without the former.
> There doesn’t appear to be any kind of polymorphism—over types, effects, refinements, or proofs.
Yep. Haven't needed it yet, and I've erred on the side of simplicity and leaving things out.
ingve linked to the github page instead of the announcement e-mail: https://groups.google.com/forum/#!topic/puffslang/2z61mNTAMn...
That announcement has more to say about the comparison to Rust, which is probably the most frequently asked question.
There's also some more words, on Rust and on other related work like Dafny, at https://github.com/google/puffs/blob/master/doc/related-work...
Edit: It's also not Rust per se, but the numbers at https://github.com/google/puffs/blob/master/doc/benchmarks.m... shows that, on Puffs' benchmarks, gcc 7.2 noticably outperforms clang/llvm 5.0. I'm sure this is a solvable problem, and not a fundamental flaw with llvm, but fixing that's beyond my llvm knowledge.
I don't think you'll win any friends in the Rust camp by making broad statements like that. Performance of "unsafe" vs "safe" languages largely boils down to memory access patterns, cache usage and how many levels of indirection you tend to hit. Rust easily matches C/C++ while keeping high level abstractions(bounds checks are done at slice level and not per-element access).
I don't think Mozilla would have used Rust for Servo/Quantum if it was slower than C++. It's certainly held true in all the cases I've used Rust in place of C++.
And, yes, memory access patterns, cache usage, etc. affect performance.
And, yes, in general, Rust performs comparably to C/C++. As I noted elsewhere, Rust with runtime arithmetic overflow checks currently performs worse than Rust without such checks. So, yes, Rust without those checks is as fast as C, and in general, arithmetic overflow isn't the biggest concern.
steveklabnik, a Rust expert, commented on this page that, in the future, "if the runtime checks get cheap enough, we can do them in release mode as well". If so, that's great, I'm happy to be proven wrong. Cheap still isn't zero, though, and see "nanoseconds become milliseconds" at https://groups.google.com/forum/#!topic/puffslang/2z61mNTAMn...
In contrast, Puffs today performs as fast as C, with arithmetic overflow checks. They just happen to be compile time checks. And sometimes overflow is indeed a concern (search for "underflow" in https://blog.chromium.org/2012/05/tale-of-two-pwnies-part-1....).
I'm sorry, but I don't understand what you mean by bounds checks being done at the slice level and not per-element access. A statement like "pixels[y * stride + x] = etc" is per-element, right?
Yes, if you index a slice you have to check each access. However the idiomatic way to work with strides of data in Rust is to use Iterators.
Bounds is checked at the entry of an iteration and the the inner loop is nice and fast. So your example would be:
for pixel in &mut pixels[0..y*stride+x] {
*pixel = etc
}
I tried to do something similar on the playground[1] but it turns out Rust/LLVM is too smart and folded the whole loop down to a constant.[1] https://play.rust-lang.org/?gist=f3699d6456a561c3874395bff36...
I'll grant that's largely true, but as far as I can see it's not entirely true. For example, to quote a comment from one of the example .puffs files:
// Set history_index (modulo 0x8000) to the length of this
// remainder. The &0x7FFF is redundant, but proves to the
// compiler that the conversion to u32 will not overflow.
In this case, the redundant operation is equivalent to a disabled arithmetic overflow check, although it's (commendably) made more explicit.Similarly, in decode_lzw.puffs, I think (could be wrong) that some of the conditions in 'if' statements, e.g. "(width < 12)", should always be true unless the image is malformed. In a way, this is substituting for a runtime array bounds check and/or overflow check that might be hit if the condition weren't present. Here too, Puffs arguably wins by making the check explicit. If I'm right that the author stuffed in extra conditions where demanded by the compiler, rather than properly considering the consequences of their being false (e.g. should probably result in returning an error), then there's some argument that it would be better to crash than silently misbehave. But even then, at least Puffs makes the problem relatively obvious, whereas in e.g. Rust you just have to trust that your code won't perform any overflows.
> And sometimes overflow is indeed a concern (search for "underflow" in https://blog.chromium.org/2012/05/tale-of-two-pwnies-part-1....).
Integer overflow is a huge concern in C, but much less of one in memory-safe languages, where a buffer size being miscalculated should result in a controlled panic rather than memory corruption (…at least if you're not interfacing with unsafe code). Still, an overflow is effectively a miscalculation, incorrect behavior, and with any kind of incorrect behavior there's always a chance it will compromise security in some way.
Sure, but that's not the idiomatic way to do that in Rust. The idiomatic way to do that in Rust is to have an iterator over the bytes and call `next()` on it to get the next byte. This means no unsafe code in your codebase, meaning no need to worry about proving that it's safe or about safety not being enforced by the compiler.
One of the goals of Puffs is to eliminate that runtime bounds check, so it'd be exactly as fast as C/C++'s "x = *src++", even if you're not consuming exactly one byte per iteration, so you can't use Rust's "for val in bytes_iter".
In another comment on this page, I posted to where I asked https://users.rust-lang.org/t/iterators-and-eliminating-all-... for more feedback from the Rust community about this.
One question about this to people more versed in language compilation - wouldn't it be possible for SAFE Rust code to be faster than C code considering that the Rust compiler has much more syntax to play around and optimize? Cause many of those safe constructs are part of the language and can be taken into account
In practice… maybe. However, I find we're more forgiving of unpredictable performance (either wrt analysis runtime or quality of analysis) in safety checking tools than in code generators. The ergonomics of bitblasting code generation questions to SAT aren't great.
That said, optimization is all about guarantees: this broad of a claim (UB vs info) because both are about what's guaranteed, and what's not. That is, they play similar roles, so you'd have to be comparing how much of each is in what amount in what code.
We generally expect Rust to be roughly as fast as C, sometimes faster, sometimes slower. Modulo optimizer bugs in each case, of course.
In practice, LLVM hasn’t had any incentive to implement those sorts of optimisations at this stage (because before Rust came along nothing would benefit from them), so the benefits are generally theoretical only.
It remains to be seen what this will effect.
C:
int foo(int *x, int *y) {
*x = 0;
*y = 1;
return *x;
}
gives: foo(int*, int*): # @foo(int*, int*)
mov dword ptr [rdi], 0
mov dword ptr [rsi], 1
mov eax, dword ptr [rdi]
ret
where as in Rust: fn foo(x: &mut i32, y: &mut i32) -> i32 {
*x = 0;
*y = 1;
*x
}
which compiles to: example::foo:
mov dword ptr [rdi], 0
mov dword ptr [rsi], 1
xor eax, eax
ret int foo(int *restrict x, int *restrict y) {
*x = 0;
*y = 1;
return *x;
}
Compiles to: foo:
movl $0, (%rdi)
movl $1, (%rsi)
xorl %eax, %eax
retDoes LLVM implement optimization that only Rust can use, or at least those that cannot be used from C/C++?
LLVM is adding some semantics specific to non-C or C++ languages. I’m on my phone so I can’t link you, but they’re adding an intrinsic related to infinite loops because languages like Rust have different semantics here.
Puffs lets you specify in the code that you do not intend to allow arithmetic overflow at all. It also has types to distinguish pointers that may be null and pointers that must not be null.
Puffs enforces arithmetic overflow checks (it's not optional, and you don't have to specify that you intend to check overflow). It differs from Rust in that it's done entirely at compile time instead of at run time.
What's the comparison in speed between an average C programmer vs an average Rust programmer? Rust usually defaults to the fast thing, which can lead to performance surprises when it doesn't. But I think this question is more interesting than "what can the best C coder do vs the best Rust coder", the average is what is going to be the case for a much bigger segment of programmers.
Secondly, what's maintainable? For example, take the Stylo and Firefox devs: you can absolutely do what Stylo does, but in C. But can you maintain it? Rust's safety guarantees let you do very aggressive things that are too tricky in C. Yes, you could write the code. When you come back three months later, and modify the code, will you make a mistake? The Rust compiler will catch you here, but the C compiler probably won't...
A related version of this story: one of the core devs talks about working on some code that needed reference counting deep in the core. He used Rc, which is non-atomic, because the original case was not threaded. He came back a few months later, trying to paralellize it, and got a compile-time error, pointing to the guts that used the un-threadsafe code. He was able to immediately fix it, whereas if the compiler couldn't have checked this kind of thing, it would have been a subtle, hard to reproduce bug.
TL;DR: these kinds of comparisons are very difficult and often anecdotal. We'll see empirically as time marches on :)
I wrote quite a bit of C and I like the language as it really fits its purpose (the thinnest and usable layer above Assembly) but I would never want to create/maintain a big application (especially threaded) in it
Yes. Rust has TONS of aliasing info that optimizers essentially spend a lot of time recovering in other programs. However, LLVM doesn't support being told about all of it, and doesn't make use of it that much directly (we can at best tell LLVM that a whole bunch of things are `restrict`). In fact, we had to stop doing this for `&mut` because LLVM had too many bugs around that. C/C++ codebases don't benefit from this as much so LLVM doesn't prioritize this.
Rust can do optimizations at the MIR level and I'm hoping we'll do more in that space in the future.
Dependent types are meant to solve exactly this class of problem. Rust has an RFC for adding these:
edit: also thanks for the link :)
Puffs does not use infinite precision integers (aka "big ints"), for performance. But Puffs still checks for arithmetic overflow.
Re dependent types, I made a comment elsewhere on this page that "dependent types are one way to prove bounds safety, but they're not the only way".
I'm all for having different implementations of software, but does Rust not fulfill these requirements?
And there are several nice parsers to choose from, e.g.:
https://github.com/rust-lang-nursery/rust-bindgen/blob/maste...
Its support is somewhat limited, but… using a Cfront-like approach wouldn't help the situation much, aside from increasing portability.
After all, a given platform's C++ ABI usually looks like the C ABI, with some things added on top that could be represented explicitly at the C source level (e.g. inheritance becomes composition; name mangling; hidden fields for vtables; hidden parameters for 'this'; etc.). In other words, C++ compilers already act like Cfront followed by a C compiler. By the same token, C++ functions and structs/classes can be accessed explicitly through a C FFI by doing the extra stuff manually (or having a tool like rust-bindgen do it for you). There are a whole bunch of exceptions, e.g. Windows using a special register to pass 'this', but in theory those can mostly be handled through some ad-hoc additions to the FFI; many of those additions haven't yet been implemented in Rust [1], but they will be.
There is a more fundamental limitation, though. If you have, say, a C++ template function in a header file, it needs a C++ compiler to translate it to machine code for every set of template parameters it gets used with. If you want to call it with arbitrary parameters from Rust, you'd have to have a C++ compiler built into your Rust compiler. But a Cfront-like tool wouldn't help with that. It couldn't, say, translate a template function to a single C function that doesn't have to be monomorphized; C++ gives you too much power at compile time for that to work.
[1] https://github.com/rust-lang-nursery/rust-bindgen/issues/849
I should also add that Rust is great tech, written by great engineers. Puffs is still different (with different trade-offs), as per the links in my other comment.
assert n_bits < (width + 8) via etc
The 'via' syntax is discussed at https://github.com/google/puffs/blob/master/doc/puffs-the-la...
In the beginning I had thought that maybe you were using something like Pentagons: https://www.microsoft.com/en-us/research/publication/pentago... which were developed explicitly in the context of proving array bounds checks.
You might want to give them a look. They would cost you in implementation effort, of course, but they might make Puffs (almost as) fast and pretty smart!
And yep, I'm also Terry's brother.
This is kind of a weird thing to say, because Rust is as fast as C, and can be used to create C-compatible libraries.
http://netbsd.gw.com/cgi-bin/man-cgi?puffs+4.i386+NetBSD-7.1
What do I have if I see libpuffs.so somewhere?
As for the "We're google, we can pick any name we want" assumption that somebody else wrote earlier, https://github.com/google/puffs says "Disclaimer: This is not an official Google product, it is just code that happens to be owned by Google". Also, before the launch, I didn't find many hits for "puffs" when searching github.com projects, or searching "apt search puffs", or searching the Web in general for queries like [puffs programming], or uses of the ".puffs" file extension. It's not like I cackled maniacally as I deliberately screwed over the NetBSD puffs project, whether for myself or on behalf of Google, I just didn't find it. I am bad at searching. Sorry.