Search code examples
javapythonclinuxsigbus

Does there exist a surefire, cross platform way to reproduce a SIGBUS?


This question is out of pure curiosity; personally I have seen this signal being raised, but only rarely so.

I asked on the C chatroom whether there was a reliable way to reproduce it. And on this very room, user @Antti Haapala found one. At least on Linux x86_64 systems... And after some fiddling around, the same pattern was reproducible with three languages -- however, only on x86_64 Linux based systems since these were the only systems this could be tested on... Here's how:

C

$ cat t.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

int main () {
        int fd = open ("empty", O_RDONLY);
        char *p = mmap (0, 40960, PROT_READ, MAP_SHARED, fd, 0);
        printf("%c\n", p[4096]);
}
$ :>empty
$ gcc t.c
$ ./a.out
Bus error (core dumped)

Python

$ cat t.py
import mmap
import re
import os

with open('empty', 'wb') as f:
    f.write(b'a' * 4096)

with open('empty', 'rb') as f:
    # memory-map the file, size 0 means whole file
    mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)

    os.system('truncate --size 0 empty')

    b'123' in mm
$ python t.py
Bus error (core dumped)

Java

$ cat Test.java
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Random;

public final class Test
{
    private static final int SIZE = 4096;
    private static final Path VICTIM = Paths.get("/tmp/somefile");

    public static void main(final String... args)
        throws IOException
    {
        // Create our victim; delete it first if it already exsists
        Files.deleteIfExists(VICTIM);
        Files.createFile(VICTIM);

        final Random rnd = new Random();
        final byte[] contents = new byte[SIZE];
        rnd.nextBytes(contents);
        Files.write(VICTIM, contents);

        try (
            final FileChannel channel = FileChannel.open(VICTIM,
                StandardOpenOption.READ, StandardOpenOption.WRITE);
        ) {
            final MappedByteBuffer buffer
                = channel.map(FileChannel.MapMode.READ_ONLY, 0L, SIZE);
            channel.truncate(0L);
            buffer.get(rnd.nextInt(SIZE));
        }
    }
}
$ javac Test.java
$ strace -ff -o TRACE java Test
Exception in thread "main" java.lang.InternalError: a fault occurred in a recent unsafe memory access operation in compiled Java code
    at Test.main(Test.java:35)
fge@erwin:~/tmp$ grep -w SIGBUS TRACE.*
TRACE.15850:rt_sigaction(SIGBUS, NULL, {SIG_DFL, [], 0}, 8) = 0
TRACE.15850:rt_sigaction(SIGBUS, {0x7fe3db71b480, ~[RTMIN RT_1], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x7fe3dc5d7d10}, {SIG_DFL, [], 0}, 8) = 0
TRACE.15850:--- SIGBUS {si_signo=SIGBUS, si_code=BUS_ADRERR, si_addr=0x7fe3dc9fb5aa} ---

Again: all the examples above are only on Linux x86_64 systems; I have nothing else at my disposal.

Would there be a way to reproduce this on other systems?

Side questions: if the examples above were reproducible on systems not having SIGBUS, what would happen?


Solution

  • SIGBUS is one of the perils of using memory mapped files. According to POSIX, you get a SIGBUS with respect to mmap() in the following conditions:

    The system shall always zero-fill any partial page at the end of an object. Further, the system shall never write out any modified portions of the last page of an object which are beyond its end. References within the address range starting at pa and continuing for len bytes to whole pages following the end of an object shall result in delivery of a SIGBUS signal.

    An implementation may generate SIGBUS signals when a reference would cause an error in the mapped object, such as out-of-space condition.

    In your example, the object referred to by fd has a length of 0 bytes. All pages of the mapping are therefore “whole pages following the end of an object” and generate a SIGBUS on access.

    You can avoid a SIGBUS by not mapping more pages than the object is long. Sadly, there is an inherent problem with MAP_SHARED: Even when the length of the mapping was verified to be correct on mmap(), the object size may change afterwards (e.g. if another process calls truncate() on the file).

    Generally, you always get a SIGBUS when you access a page that is not mapped. Linux generates SIGSEGV in some of these cases, as the semantics overlap. SIGSEGV should be generated when a page is accessed in a forbidden way.