
Segfaults are our friends and teachers - kalmar
http://kamalmarhubi.com/blog/2016/04/25/segfaults-are-our-friends-and-teachers/
======
kibwen
To be absolutely clear, the behavior exhibited in the OP is indeed considered
a bug by the Rust developers: see [https://github.com/rust-
lang/rust/issues/16012#issuecomment-...](https://github.com/rust-
lang/rust/issues/16012#issuecomment-165589146) for the latest discussion.
TL;DR: this currently isn't exploitable on Windows, and patches to LLVM adding
support for stack probes will ideally allow this to be easily solved for non-
Windows platforms as well.

------
Annatar
From the article:

> The Rust program got a segmentation fault because it attempted to write to
> inaccessible memory, but only through the stack pointer. None of the
> undefined behaviors disallow this, which I think is why it’s ok for this
> Rust program to segfault.

What I got out of that is that Rust _does not work as advertised_ if there are
still situations where a program could segfault. The entire premise of Rust,
as I understood it at least, is that it does things in a safe manner and the
programmer does not have to worry about it. Now I learned that there are
undefined behaviors. In my view, for a language that bills itself as safe,
there should not exist such things as undefined behaviors. As far as I am
concerned, then, based on the advertising of Rust, this is _false
advertising_.

~~~
steveklabnik
It's not false advertising; it's a bug. Rust is not perfect. It's also not
undefined behavior. [https://github.com/rust-
lang/rust/issues/16012](https://github.com/rust-lang/rust/issues/16012)

(Basically, stack probes are only implemented on Windows, and we need them on
the other platforms, but it hasn't been implemented yet, for various reasons.)

~~~
Annatar
Okay, but such a bug means Rust is not ready for prime time yet, so this
situation then ends up being the case of _don 't believe the hype_.

~~~
wofo
I think steveklabnik is right. For instance, take a look at this Java bug,
which is relatively recent:
[https://bugs.openjdk.java.net/browse/JDK-8046516](https://bugs.openjdk.java.net/browse/JDK-8046516)

On the same grounds you could label Java as a language "not ready for prime
time yet".

~~~
Annatar
From a perspective of an assembler, C, shell, and AWK programmer, Java has
never been, nor will it ever be ready for anything. We use it at work (we run
thousands of Java applications, written externally and in house), and it is a
total piece of shit: 300 GB of RAM consumption on the average(!!!), CPU
intensive, slow, not backwards compatible (try changing the Java runtime
environment under an application and see how well that works for you). From my
experience and point of view, Java is overcomplicated and unintuitive, even
for the simplest of tasks. Furthermore, I deeply regret that Java was one of
my core computer science courses at the university (way back when Java was all
the rage). Personally, I would rather drop dead than use Java, and if they
tried to force me to write code in it again, I would quit on the spot!

If I had it my way, Java would be a criminal offense carrying a minimum prison
sentence of at least 20 years, and if I ever have my own company and I catch
someone using Java, I will fire them on the spot with so much gusto! It will
be so awesome.

------
Sean1708
For those wondering about segfaults specifically in Rust (I know it's not the
point of the blog post but it might be interesting to others), this thread
talks about why they occur/whether they'll ever be eliminated entirely:

[https://users.rust-lang.org/t/rust-guarantees-no-
segfaults-w...](https://users.rust-lang.org/t/rust-guarantees-no-segfaults-
with-only-safe-code-but-it-segfaults-stack-overflow/4305)

~~~
steveklabnik
One small update to that thread: hitting the guard page no longer sends
SIGSEV, but SIGILL: [https://github.com/rust-
lang/rust/pull/31333](https://github.com/rust-lang/rust/pull/31333)

------
EliRivers
The first sample code; "This program segfaults because the entire stack is set
to 0 at program start."

I'd be surprised; as a strong general rule, the stack does not get zeroed
[Edit: see end of thread! It's the OS zeroing everything - learn something
very day]. I'd expect it to segfault because the pointer value is whatever
leftover non-zero value happens to be in that piece of memory, so it points
into random memory the user program shouldn't be messing with (sticking in a
printf to output the value of the pointer confirms this on at least one
system). Wouldn't be surprised if some implementations took security really
seriously and zero everything, or if a debug build was zero happy, but under
normal circumstances, the stack doesn't get zeroed.

~~~
dkopi
edit: exDM69 actually makes more sense.

~~~
EliRivers
Well, I can see the build command on the webpage there, and assuming this is
the same gcc we all know and love without any cleverness going on, it's not a
debug build of any kind as far as I can see:

    
    
        gcc segfault.c
    

Now that I think about it, I'm not aware that gcc C compiler even offers the
option to deliberately zero initialise non-static local variables, so unless
I've missed a switch somewhere there is no arrangement of options available
for a "debug build" to do this. I recall the GCC Fortran compiler did offer
it.

~~~
exDM69
It's the OS linker/loader that zeros the pages used by a new process to avoid
leaking memory contents of previous processes (security risk).

It's zero only because this occurs at the very beginning of the program. In
general it would be undefined.

~~~
Buge
The OS will initialize the memory of the stack to 0 before the program starts.
But before main is called, the compiler is free to insert other code that runs
before main. gcc inserts a function named __libc_start_main that runs before
main. This code will modify the content of the stack. So when main is run, the
stack where the uninitialized local variable is has a decent chance of not
being 0 anymore.

This is easily testable.

    
    
        #include <stdio.h>
        
        int main(void) {
            char* pointers[20];
            int i;
            for (i = 0; i < 20; ++i) {
              printf("%p\n", pointers[i]);
            }
            return 0;
        }
    

And yes when I run it, most of the pointers are not null.

~~~
exDM69
Sure, the C runtime initialization runs before main. Unless you're looking at
the stack at _start, it's probably unintialized. And it depends on your libc
implementation, etc.

It just happened to be zero in the author's case.

------
Szpadel
> Curiously, I found that if I had a buffer size of even 1 byte over (8 MB - 8
> KB), I still got the segfault. I’m not yet sure what’s going on there!

This is because of gcc padding. Programs have to allocate whole page from OS.
So if you want just 1 int, you have to get whole page for it (compilers can
optimize it in some conditions). This is result of MMU that works for memory
block and not for single bytes (performance issue I think) But as I know by
default page size have 4KB.

Another reason may be that compiler tries to allocate 2^n bytes because of
performance. and 8KB is close enough I think.

~~~
dfox
The main problem there is that local non-static variables get placed onto
stack. Stack space is allocated by generated code by just decrementing stack
pointer without any explicit calls to OS. On typical modern unix, only few
pages of stack are actually mapped and kernel handles page faults on
neighboring pages by allocating more stack pages. Because it is possible that
function needs more than one page of stack space, there is more than one such
"magic" stack page, but still there is some finite number of them (othervise
there would be no way to distinguish between accesses beyond the end of stack
that should grow stack and accesses to random unmapped memory). Thus if you
allocate some ridiculously large things on stack and access them in the
direction opposite of stack growth, you may get segfault (accessing these
structures in "the right order" is by no means sufficient for this to be safe
because there are things like red-zones, signals, other local variables...).

This is true only for first thread, other threads have fixed stack size
specified on thread creation (on Linux it's 8MB by default), but usually even
thread's stack pages are really allocated only on first access.

On UNIX if you really want to bump stack by arbitrary amounts the most
portable way is to preallocate your own stack of sufficient size and then use
that (either by abusing sigaltstack() or via makecontext()/setcontext() or
possibly by creating new thread). But generally, having large local variables
is not exactly good idea.

~~~
bogomipz
Could you elaborate on "Stack space is allocated by generated code" Are you
talking about the C runtime in the binary? Thanks.

~~~
dfox
No. Typical C function looks something like this:

    
    
      push %rbp
      mov %rsp, %rbp
      sub $something, %rsp
      ... actual code ...
      mov %rbp, %rsp
      ret
    

The sub $something, %rsp instruction is everything that user-space does to
allocate stack memory. Actual allocation of stack pages happens in kernel in a
way that is completely transparent as long as the $something does not get
unreasonably large.

------
amelius
> Segfaults are our friends and teachers

Too bad memory is not better segmented then. For instance, when linking
against a library, that library's memory ends up in the same "segment" as the
program itself. Therefore, right now, you can totally screw up a library's
internal data structures without even causing a segfault directly.

------
userbinator
_These are called guard pages. Attempts to write there would result in a
segmentation fault._

...which are caught by the OS and used to either truly kill the process when
the stack overflows, or to dynamically allocate more memory as the stack grows
downwards. That's how it works on Windows, at least; I'm not as clear about
Linux.

------
catern
The robust solution to this problem is not hardcoding the pipe buffer size and
changing the size of pipe buffers within your program to match your hard coded
value, but rather calling fgetconf to query the pipe buffer size for the pipe
FD you are working with.

