Live data from Hacker News

Regex: badly needs fuzzing

svn.boost.org

141–150 of 180 posts

Re: Regex: badly needs fuzzing

#141

Earlier quoted context omitted.

No, but not everything needs to be memory safe. That's what the kernel is there for. You can typically just restart the program. The classic program safety vs programmer time tradeoff.

> No, but not everything needs to be memory safe. That's what the kernel is there for. The kernel doesn't automatically defend against RCE.

I should have specified more clearly

by:

> That's what the kernel is there for.

I meant, you should still be able to easily restart your program and continue using it. It wont crash your computer.

RCE is only a problem for computers that are connected to the internet and/or take user provided input.

Re: Regex: badly needs fuzzing

#142
post #133

Earlier quoted context omitted.

Neat! What was the application? Or was it pure research?

For a content filtering/scanning service. By the time we wrapped up this project Intel finally released Hyperscan as Open Source. Hyperscan implements these and other transformation tricks, not to mention the SIMD optimizations. However, Hyperscan doesn't have as strong compatibility for PCRE as what we ended up with--100% effectively--and would have necessitated keeping libpcre as a failover. Hyperscan is substantia…

> It allows yielding and resumption of matching at any and all input stream boundaries [whereas] Most regular expression tools require a complete string to match against

Yes! This is how I've always implemented from-scratch pattern matchers, even for toy systems. To be fair, about 75% of it comes down to it being the only way that feels natural. But on the other hand, even if it's something you don't need now, the alternative all-at-once approach is so inflexible that, should you ever come to need it, then it's basically impossible to refactor an existing implementation to have this kind of orthogonally-cutting "re-entrancy". So you'll have to either throw out the whole implementation and start over, or just try to ignore it and be forced to work around it as you bump against it time and time again as punishment for not doing it right the first time.

Re: Regex: badly needs fuzzing

#143
post #120

Earlier quoted context omitted.

Memory-safety bugs can lead to RCEs, which are considered unacceptable risks to most in computer science. Additionally, as Rust shows, memory-safety bugs can be checked by a computer, which can consistently apply those checks, making it an excellent bang-for-buck to use Rust or a comparable memory-safety checker. That's what pcwalton is saying.

How can you say these are considered unacceptable risks to most, when people write so much code in C and C++? These risks are widely accepted and people are trying to mitigate them using various methods. And that's what I'm trying to communicate: they're only unacceptable to pcwalton and the Rust community.

> How can you say these are considered unacceptable risks to most, when people write so much code in C and C++?

This argument was very compelling in, for example, 1997. But, nowadays, most code is written in memory safe languages. Choosing to write your next Unix daemon in Go or your next Windows app in C# is not exactly an uncommon choice in 2017.

> And that's what I'm trying to communicate: they're only unacceptable to pcwalton and the Rust community.

I'm not a member of the security community, but I have never seen anyone in that community disagree with my claim that C and C++ are unsuitable for writing secure software at scale (at least without a robust sandbox or restrictive subsets). I have seen the claim that sandboxes are sufficient to mitigate memory safety problems, but even in that case the comparison becomes "C and C++ with a correctly maintained, leak-proof sandbox" vs. "a memory-safe language", not "C and C++" vs. "a memory-safe language".

Re: Regex: badly needs fuzzing

#144
post #136

Earlier quoted context omitted.

What you were doing is advocating Rust in a C++ thread, again. Safety first, and all that. Congratulations on getting voted to the top of this topic, but it's tiresome. Let's pretend C++ is a car. I get in my car, and I drive somewhere. Yes, there are hundreds of thousands of accidents per year, but really, most people get where they want to go, and we aren't all dead. I've had my share of fender benders, but no RCE…

> no RCE has ever been exploited in 25 years of my C or C++ code I'm genuinely curious about you say this with any sort of surety. Do you have any sort of, say, crash reporting from users' computer, or some other way to know if a problem occurred in the wild? (Not that these will actually detect a successful RCE, only failed ones.) Additionally, not being (known to be) exploited doesn't mean that much without more co…

> I'm genuinely curious [how] you say this with any sort of surety.

Of all the things I said, is that really the only one you want to address?

In the future, I'll try harder to qualify my statements with a "to the best of my knowledge" clause when replying to Rust core developers, but constantly adding caveats to every word I say is tedious. No, I'm not omniscient - I can't prove my software has never been exploited.

I have some doubts whether your question is really all that sincere, but giving you the benefit of the doubt: For the last 15-20 years, my job-related software runs on networks which are effectively air-gapped. My users aren't shy about submitting bug reports, and they generally have my phone number. For what it's worth, truly "malicious users" run the risk of getting fired or facing a court-marshall. Anyone who runs my code already has a shell and a compiler on the machine. We give them the source. They frequently have sudo. It would be much simpler for them to just write a program and run it than inject it into my (hypothetical) buffer overflows. If they want to crash my software, they can simply kill -9 it (or not start it in the first place).

Before that I worked for a few unsuccessful startups, and I wish my software had seen enough exposure to run the risk of being exploited. I've written Netscape plugins which I know were exploitable, but that code vanished along with the stock options before most people had gotten past dial-up connections. Maybe someone somewhere curses the day I was born, but they didn't send a bug report.

Having said all of that, you probably think I'm in some rare position. However, it's a really big world, and you might be surprised how much sloppy C, C++, Fortran, and COBOL software is out there quietly getting the job done without the constant onslaught of black hats attacking it. We don't all write web browsers and servers. There are a lot of potentially profitable C++ targets in the finance industry, but somehow they survive.

I'm not one of the people saying modern C++ solves all the problems. I'm keenly aware of many short comings in C++. The language sucks in a lot of ways, and I dread trying to explain the complexities to non computer science developers.

Given all of that, I have been interested in Rust for reasons having nothing to do with safety. You have some great features, and I think you should advertise those. If you fixed the pain points in Rust instead of emphasizing the shortcomings in C++, I think you could win a lot more converts (and a lot of new developers who could grow your ecosystem outside of web clients and services).

Re: Regex: badly needs fuzzing

#145
post #132

Earlier quoted context omitted.

> That statement is dangerously close to a No True Scotsman It also had a smiley in it. I'm pretty sure you missed the joke and don't realize you're preaching to the choir.

This is Poe's law territory, e.g. a lot of your own comments sound very similar to the parent, and there are other emoticons more commonly used to indicate sarcasm/joke.

> This is Poe's law territory

Heh, I think that's possibly the only virtue to sarcasm: it confuses the pedants. I use friendly smileys or the word "heh" when I'm trying to be jovial, and I'm willing to give that poster the benefit of the doubt. :-)

> a lot of your own comments sound very similar to the parent

Which comments?

> there are other emoticons more commonly used to indicate sarcasm/joke

I think any common standard about something like this probably only applies to a narrow clique.

Re: Regex: badly needs fuzzing

#146

Earlier quoted context omitted.

We aren't talking about eliminating all bugs. We're talking about eliminating memory safety problems, which frequently result in RCE.

What you were doing is advocating Rust in a C++ thread, again. Safety first, and all that. Congratulations on getting voted to the top of this topic, but it's tiresome. Let's pretend C++ is a car. I get in my car, and I drive somewhere. Yes, there are hundreds of thousands of accidents per year, but really, most people get where they want to go, and we aren't all dead. I've had my share of fender benders, but no RCE…

> Do you really think a true expert couldn't make a safe C++ regex library?

I do believe that a C++ regex library written in reasonable time using normal development practices will have memory safety problems in it. This is based on the real-world experience we have with C++ projects.

> Yet another time, I find out you really can't accomplish the task without writing unsafe code, so the compiler really wasn't going to protect me from myself anyways [2].

If your project is 5% unsafe code and 95% safe code, that's a win due to isolating the trusted computing base. In fact, when you get down to it, this sort of setup describes all Rust projects, as the standard library has unsafe code.

> [1] Honestly, I think you guys are screwed on that one. I suspect you're going to have to break backwards compatibility to really fix it, so I believe you'll leave it broken instead.

I don't agree that it's broken. In order to "fix it", we'd either have to throw out generics in favor of C++/D-style templates or have Haskell-like overlapping instances link errors. Each of those options is unpalatable. You lose some expressiveness, sure, but writing newtype wrappers is not the end of the world.

In general, I find issues with the expressiveness of generics are often overblown. Remember that people write all sorts of software in Go, which doesn't have generics at all, much less overlapping instances!

Re: Regex: badly needs fuzzing

#147

Badly needs static analysis . coverity is free for open source projects, will find many of the same issues, and produces reports that directly identify the buggy code, rather than the crash which might come thousands of instructions later in a completely different layer, requiring extensive reverse engineering to identify the source. And then when you've fixed one, the other test cases all need to be re-tested becaus…

Needs both.

> rather than the crash which might [...]

Assert like mad. Break out your debug allocators, your asan, your checked iterators. Fuzzing can be as bad as you say, but it can also give you extremely easy to diagnose repro cases - and catch an incredibly wide range of issues for incredibly little work, in my experience.

Static analysis generally won't tell you "Hey, if I almost-but-not-quite exhaust your address space allocating to successfully parsing this file, I can get your next closed-source API call to segfault!" - useful for tracking down each and every possible allocation related to networking deserialization so you can configure sane quotas (such as "array size probably shouldn't be larger than bytes sent, bail out safely instead of trying to allocate that")

With most sane setups that come to mind, no false positives either.

> And then when you've fixed one, the other test cases all need to be re-tested because they might have come from the same root cause, despite crashing in different locations.

Your tooling should do this automatically for you. If it doesn't, get better tooling. AFL. SDL MiniFuzz. Whatever floats your boat.

> Particularly when the hit rate is very high, fuzzing is a stupidly inefficient way to find bugs.

I've had it be stupidly efficient in terms of programmer time once setup. Occasionally leaving my computer on overnight? Worth.

Re: Regex: badly needs fuzzing

#148
post #70
post #25

Earlier quoted context omitted.

In my experience, the exponential time thing isn't really a big deal. I've used Perl regular expressions on a very regular basis for about 16 years now. Exponential time has been an issue only once. Obviously if I were accepting regular expressions from random people, I'd use RE2. But for my day to day purposes, it's pretty much a complete non issue.

re2 is not only about exponential time: matching of regexes like a|b|c is O(N) in backtracking engines and O(1) in DFA-based engines like re2. It can make a big difference in practice for generated regexes - e.g. if you want to check if an URL has one of the thousand substrings in it (think adblock-like use cases). With backtracking regex or with a loop it'd be O(N) regarding the number of options, but with DFA it is…

How does the DFA engine match anything without looking at the whole input?

Re: Regex: badly needs fuzzing

#149
post #133

Earlier quoted context omitted.

For a content filtering/scanning service. By the time we wrapped up this project Intel finally released Hyperscan as Open Source. Hyperscan implements these and other transformation tricks, not to mention the SIMD optimizations. However, Hyperscan doesn't have as strong compatibility for PCRE as what we ended up with--100% effectively--and would have necessitated keeping libpcre as a failover. Hyperscan is substantia…

(Hyperscan team lead here) Interesting. I should point out that Hyperscan is not abandonware; it is still maintained by Intel. Streaming is not unique to Ragel. You're not wrong about libpcre compatibility. We have very tight syntactic compatibility with libpcre (that is, we won't misidentify an unsupported construct and supply some erroneous semantics) but we make no secret of our inability to handle general lookaro…

As I alluded to earlier, the 100% (possibly +/- edge cases) solution was finished primarily by a contractor.[1] So the following only reflects the state of things while my Lua parser, analyzer, and transformation code was still in use. Also, my grasp of this stuff is nowhere near as strong as your's or the contractor's. I'm a pretender ;)

Backreferences are implemented with prefilters, NFA machines, and what are effectively zero-with assertions. It's not sexy, and the rest is easy to sketch out from there as a conceptual matter. What's more interesting are the Ragel-specific details.

Before we tackled backreferences we had a problem with repetitions causing unacceptable state blow up with unioned DFAs, repetitions, semantic conditions, and especially with their interactions. This was the biggest barrier for most of the final 10%. To help solve that the contractor added an NFA construct to Ragel, along with the magic needed for the generated Ragel state machines to switch from DFA to NFA modes mid-expression. The NFA construct expands on the notion of embedded host-language code in transition actions and semantic conditions, which allows embedding inline C code to track and control the matching behavior of each subexpression, as well as to handle bookkeeping, including state stack management.

Most simple captures can be accomplished using Ragel's regular action features. But IIRC even captures that could be done from a DFA machine would sometimes blow up state too much so I think the captures necessary for backreferences are always done in NFA mode. Similarly, the code is quick to fallback to NFA mode for expressions without backreferences as it simplified grouping some of the complex expressions into secondary unions, which allowed compiling their prefixes as a DFA; as opposed to immediately falling back to prefilters and a linear match of each expression that couldn't be added to the primary or secondary unions.

Matching of backreferences is implemented similar to lookarounds. Lookarounds, at least in my early code, are implemented using semantic conditions. In my later code I think lookbehinds still had to be a fixed size, but it looks like (bugs notwithstanding) backreferences could be variable sized. The Ragel match is anchored on the empty string at the relevant position using the NFA construct, auxiliary code handles matching against the capture, and on a match the `p' index variables is updated to skip over the length of the match. The NFA construct saves and restores `p' as it trials each subexpression. Of course this approach doesn't work well in streaming mode. Similar to Ragel scanners I think theoretically the host application could arrange for windowing, but I don't know if everything is in place to make that feasible. In our case we didn't need the streaming.

And at least in my code, anything with backreferences automatically gets a prefilter and is only run conditionally in a second phase. Presumably that's so we don't need to do captures for stuff that will never match, whether or not it involves an NFA. I'm sure that's how it's still implemented.

Regarding streaming, I totally forgot about Hyperscan's streaming mode. Streaming isn't something that (IME) can be readily bolted on after the fact, so it's a testament to it's careful design and forethought.

But in C, especially in a complex networking application doing asynchronous I/O, callbacks are so extremely costly--they break code locality and compound the normal headaches with ownership and lifetime. Managing that complexity is of utmost importance in a language like C. Using Hyperscan in streaming mode, especially in combination with DPDK... I imagine the project failure rate is really high. I know of at least one skillful team that bombed precisely as I predicted because of the excessive complexity and the resulting inability to stabilize the product. The callback/push/interrupt model is too difficult in C. In languages with lambdas, Hyperscan's streaming mode has no handicap. But especially in C, Ragel's idiosyncratic host-language interface, with it's embedded action handling and minimal state, is just so incredibly important for reducing complexity and for sticking as closely as possible to a pull model for data consumption without sacrificing performance. If developers spend most of their time working on and fixing the I/O code and related interface boundaries (as is typical), there's much less time for implementing the functional features that matter. I don't doubt people have built a ton of amazing things with DPDK and Hyperscan's streaming mode, but the number of teams that can do that _correctly_ are few and far between. Honestly, I wouldn't want a solution like that protecting my data unless I knew the team had the requisite skill.

On second thought... I should reel that criticism in a little bit. ;) For just matching and cataloging "hits", rather than performing complex operations on them, it's much less of a problem. The costs will be a function of how much work is done on the other side of the callback. But it's just so much more difficult to balance efficiency and complexity, and to constrain complexity in general. I've been writing asynchronous I/O services since before libevent and I've just become constitutionally averse to callbacks. I've never used libevent's buffering API because before it even debuted I had internalized how poorly suitable that sort of interface is in terms of interface composition. Callbacks are necessary--e.g. at the interface boundary of the event loop--but I try so hard to avoid them when possible, including switching to languages other than C when possible. It's just a huge consideration for me, perhaps unreasonably. :)

Also, I understand that Hyperscan's callback interface is largely dictated by other considerations, like the perfectly sensible choice for a runtime library interface. In streaming mode a generic queuing and retrieval interface would be awkward and suboptimal for most cases, and there's no good substitute for pushing that implementation detail to the application. DPDK, OTOH, wasn't forced to adopt an asynchronous interrupt model, it was just the easiest.

[1] I don't like to mention names of people not participating in a thread, even though in this case I'm sure you're familiar and he probably would appreciate the plug. You may have conversed with him at some point because your SIMD work is awesome, Hyperscan in general is amazing, and when it was finally open sourced we had to take a really hard look at adopting it or, alternatively, whether we should pursue SIMD optimizations on our own. And IIRC at some point he may have chatted with someone on the Hyperscan team.

Re: Regex: badly needs fuzzing

#150

Earlier quoted context omitted.

What you were doing is advocating Rust in a C++ thread, again. Safety first, and all that. Congratulations on getting voted to the top of this topic, but it's tiresome. Let's pretend C++ is a car. I get in my car, and I drive somewhere. Yes, there are hundreds of thousands of accidents per year, but really, most people get where they want to go, and we aren't all dead. I've had my share of fender benders, but no RCE…

> Do you really think a true expert couldn't make a safe C++ regex library? I do believe that a C++ regex library written in reasonable time using normal development practices will have memory safety problems in it. This is based on the real-world experience we have with C++ projects. > Yet another time, I find out you really can't accomplish the task without writing unsafe code, so the compiler really wasn't going t…

> I do believe that a C++ regex library written in reasonable time using normal development practices will have memory safety problems in it. This is based on the real-world experience we have with C++ projects.

Me too. I'm guessing the JavaScript RegExp types in Firefox and Chrome are pretty battle hardened at this point though. I submit those as existence proofs that it could be possible. However, I'm sure they've had their exploitable bugs along the way, and I would guess the code is now ugly.

For what it's worth, I would never submit all of boost as a shining example of clean and modern C++. There are gems in there, but there is a lot of cruft too. I think the biggest benefits it has brought to the C++ world is as a testing ground for new ideas and as a compiler stress test. Some parts of boost are practically a fuzz test for finding the bugs in g++ and clang++. :-)

> If your project is 5% unsafe code and 95% safe code, that's a win

Unfortunately for me, it was the first 5%, and honestly I'd call it more like 20%. It was a weekend learning project, and all I wanted to do was create a freshman level data structure from scratch. I had already had other successes, so this seemed like a good next step, but it wasn't. The weekend passed, and what I really learned was to expect more pain the next time I want to write a low level data structure in Rust.

> I don't agree that it's broken.

I can't make you agree - we might be in opinion territory. It's certainly broken in my opinion.

I have been following along in the various blog posts and forum discussions. It seems like some of your coworkers think the rules are overly restrictive, so broken or not, it looks like they're trying to fix it at least a little. Unfortunately, I don't think they're very concerned about my particular use cases (and maybe they shouldn't be).

> In order to "fix it", we'd either have to throw out generics in favor of C++/D-style templates or have Haskell-like overlapping instances link errors.

I don't think those are the only two options, but you might find the other options even more unpalatable.

> In general, I find issues with the expressiveness of generics are often overblown.

I think all this says is that you don't write very much of the kind of code which benefits from generics. You can dismiss my point of view, but some of us do use them a lot, and it's one of the driving reasons I use C++.

Generics are a documented feature of Rust, but every time I try to use them I hit a wall and end up falling back on the (admittedly powerful) macro system. If I write a library, should I tell my users they need to invoke my macros for use with their types? This is exactly the use case for generics.

(I suspect I need an obligatory smiley here to let you know it's still a friendly discussion :-)

> Remember that people write all sorts of software in Go, which doesn't have generics at all

People write all sorts of software in all sort of languages. That doesn't say much one way or the other.

Go seems really nice in some ways. The learning curve looks small, so I don't think it would frustrate my coworkers like Rust would. However, if I tried to bring it to work I'd need some sort of code generator to avoid the multiple maintenance for functions that work on float32, float64, complex64, complex128 and so on.

Maybe they'd consider adding a macro facility like the one in Rust to work around the deficiencies in the language. :-)

Besides, Go has GC and some of our software already needs a lot of memory. The pause times might be getting pretty good, but I'm guessing the memory foot print would be at least twice what it is in C++. (I should measure that though.)

> , much less overlapping instances!

Disclosure: I don't really know what an "overlapping instance" means in this context. It might be possible we're talking about different deficiencies in Rust generics, and I just don't have the right vocabulary.

Post reply on HN