3

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?

Community
  • 1
  • 1
fge
  • 119,121
  • 33
  • 254
  • 329
  • Cross-platform? Without explicitly *sending* that signal to a process surely not. It might be impossible on systems that do not even know what a bus error might be, for example embedded plain old (e.g. DOS) systems. – tofro Mar 09 '16 at 18:56
  • DOS can raise SIGBUS if you address memory that doesn't exist (which requires you to have less than 1MB RAM). – Joshua Mar 09 '16 at 18:57
  • 1
    You can call `((DirectBuffer) byteBuffer).cleaner().clean()` to unmap a ByteBuffer and if you use it again; SIGBUS. – Peter Lawrey Mar 09 '16 at 19:18

2 Answers2

3

Perhaps not what you were looking for but gets the job done.

$ cat t2.c
#include <signal.h>
int main(){raise(SIGBUS);}
Joshua
  • 40,822
  • 8
  • 72
  • 132
  • *grin* OK, you got me here... Let's say, other than purposefully sending this signal to a process? – fge Mar 09 '16 at 18:47
  • The way I do it otherwise is yank out a removable device that has a memory-mapped file on it. If you write to the memory mapping after pulling the device it's guaranteed to raise because SIGBUS also means IO failed on memory mapped file. – Joshua Mar 09 '16 at 18:48
  • Interesting... The only difference between your scenario and the one in the question is the fact that the file still exists; only that the created mapping goes beyong what the file actually contains – fge Mar 09 '16 at 18:53
3

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.

fuz
  • 88,405
  • 25
  • 200
  • 352