Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I might not be quite up to date, and I don't usually write extremely buggy code which compilers yell at, but have compilers really started warning on something like "vector_reference.push_back(&local_object);"? There are lots of ways to go wrong with unsafe memory access, and yes, there are tools to help you (things like valgrind help more than compiler warnings), but I think that the saying "hey, they gave you enough rope" is really unsatisfying. We've lived through 20 years of constant vulnerabilities because of these issues, and this only now starting to get sort of addressed. If virtually every team on the planet finds itself unable to use a certain tool "properly", then it's the tool which is being wrongly deployed, it's not the fault of everyone on the planet.

And I agree that the tool has a lot of usefulness in many places; obviously, you can't write system software without the ability to manipulate chunks of memory and the stack and everything else. But C++ is used as a universal programming language, and has tons of features which were not made for system programming, but for application programming. So on the one hand C++ has this extreme safety-oriented pedantry in which you have to decide if your design will have a reference to a const iterator or a const pointer or what, and on the other hand, you can cast that object to (void *) and write Stroustrup's picture to it.

Personally, I'd like to see a world in which the core system is written in a systems language like C/C++, and most libraries and all applications are managed code. Microsoft was working on something like this a few years ago, and I guess Android is sort of a step in that direction, but I think we won't get there for a long time. But it has to happen eventually, since we can't live with the vulnerabilities.



gcc 4.6 warns about returning the address of a local variable by default (you don't even need -Wall):

    % cat foo.c
    #include <stdio.h>
    int *bad(void) { int a = 42; return &a; }
    int main(void) { int *p = bad(); printf("%d\n", *p); return 0; }
    % gcc foo.c
    foo.c: In function ‘bad’:
    foo.c:2:30: warning: function returns address of local variable [enabled by default]
g++ 4.6 doesn't catch the error you mentioned, but valgrind does:

    % cat bar.cc
    #include <iostream>
    #include <vector>
    
    using std::vector;
    using std::cout;

    void bar(vector<int *> &v) {
        int a = 42;
        v.push_back(&a);
    }

    int main(void) {
        vector<int *> v;
        bar(v);
        cout << *v[0];
        return 0;
    }
    % g++ bar.cc
    % valgrind --track-origins=yes ./a.out
    ...
    ==8210== Conditional jump or move depends on uninitialised value(s)
    ==8210==    at 0x4EBAFE4: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/libstdc++.so.6.0.16)
    ==8210==    by 0x4EBB305: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/libstdc++.so.6.0.16)
    ==8210==    by 0x4EC62BC: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/libstdc++.so.6.0.16)
    ==8210==    by 0x400B98: main (in /home/leif/a.out)
    ==8210==  Uninitialised value was created by a stack allocation
    ==8210==    at 0x400B78: main (in /home/leif/a.out)
    ...
I don't know how hard it is to use valgrind "properly". I ran it with no arguments and it gave me the error, and even told me to add --track-origins=yes. If you're writing code that needs to be even remotely reliable and you aren't using valgrind, what the hell are you using?


Believe me, on a lot of examples of real-world code, valgrind &co. will find false positives and miss errors. I've been there and done that. And then you can't pick out the signal from the noise. Again, if just about every team on the planet has trouble with this, then it's not a question of just running some tool.

And of course we use valgrind and a bunch of other standard and custom tools. I said myself that valgrind will help here, so it's not like you need to read me the whole kindergarten primer on this.


Sorry, I don't mean to be patronizing. You asked whether the compiler catches it, it didn't, but valgrind did so I showed that.

Of course valgrind and friends have false positives, but in other languages where these errors are compile errors, you can't suppress them when you need to, instead, you just can't write that code, even if you've been alerted to the issue and know it's ok.

Of the software teams I do know, every one that was writing C/C++ (including my current one) incorporated valgrind and other tools into their build/test framework, and had no problems separating signal from noise. I don't know that many though, I'm still young. Do you know of software teams developing critical software that are unable to check their C code for memory errors automatically?


I apologize that I got testy, I just don't think there is a need to paste the whole valgrind run into the message.

Anyway, problem #1 is that valgrind is not a code-coverage tool - it only sees the execution path that just happened. You can, of course, combine it with a code-coverage tool, and people do, but code coverage is an incredibly difficult thing, since a huge number of code paths just don't happen in any normal execution. And those are the dangerous ones, both for malware attacks and for reliability.

Problem #2, which is smaller, is that valgrind is just not that perfect - for real-life code with foreign-language interfaces, real-time driver accesses, and so on and so on, it will give false positives and false negatives. This is why it has a feature where it takes a humongous error-suppression file to stop it from showing you things that you're not interested in. That's not a perfect solution.

I'm not saying there is nothing you can do - of course you can, and for well-defined projects with strong security goals and very large resources, you can get to excellent security. But even the Sun JVM had buffer overflows, I seem to remember. Those guys have heard of valgrind.

If there was a button that someone could push to make C/C++ lack of memory safety stop mattering, it would have been pushed by now.


Leif, we've reached the maximum reply depth here, which I guess means that we need to finish this up. I don't think there are people who can't use valgrind, but it just doesn't give you all that much. It lets you know that there aren't memory problems in the execution paths that you hit with your test files. But you can't test your program with every possible input; say you wrote some server - are you going to test it with every possible internal state and every combination of connections and all possible data coming over those connections? You can't. It's a gigantic search space. You can try to guess the likely problem inputs, but that's an art, not a science.

Now, malware teams use their experience/intelligence and tools (fuzzers) to find combinations of inputs that will fail your program. You won't always win a race with them - finding vulns is their only focus and main expertise. I don't see how valgrind will stop them. They'll find configurations that valgrind never saw.

The real world will also find inputs that break your program; in this case, there is just no malice.

If you run valgrind and see a clean report and say "great, no memory problems then", then you've been lulled into a false sense of security. Part of my background is in software verification, and it's never this simple even for stricter and simpler languages than C/C++.

Also, I've been involved in projects in which valgrind specifically provided tons of frustration by reporting false errors - in particular, it "didn't like" the implementation of STL which we were using, and gave us errors on half the STL allocations. This was very many years ago, and it's been improved since then, but I guess I don't have a personal experience which makes me want to trust everything valgrind tells me. But that's completely secondary to the issue of not seeing all possible execution paths.


There's just a timeout on replies that grows with depth.

Why not fuzz your own code then?


Got it, thanks, though I need to be going anyway.

Yes, you need to fuzz your code, but that's not a simple process; you need to give fuzzers very complicated information about the possible problem input for your program. And there is no reason to think that your team is better at searching for vulnerabilities than a malware team; you're in a race.


Just like valgrind and compiler warnings fuzzying is only useful in decreasing the probability of unsafe memory access. It does so at a cost and still does not solve the fundamental problem.


don't worry, I understand, I pasted it for others

Yeah, it's not perfect, but nothing we have is. I don't know what more to say. It works pretty well for most things I've seen. Do you have any examples of projects which can't use something like valgrind for some reason, not just those for which it wasn't magic pixie dust?


The valgrind comment may not have been useful to you, but it was useful to me; I didn't know valgrind could do that!


That was my point. valgrind is very user friendly. Keep using it!




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

Search: