@@ -227,6 +227,7 @@ struct snd_pcm_ops {
struct snd_pcm_file {
struct snd_pcm_substream *substream;
int no_compat_mmap;
+ unsigned int perm; /* file descriptor permissions */
unsigned int user_pversion; /* supported protocol version */
};
@@ -571,6 +571,15 @@ enum {
#define SNDRV_CHMAP_PHASE_INVERSE (0x01 << 16)
#define SNDRV_CHMAP_DRIVER_SPEC (0x02 << 16)
+#define SNDRV_PCM_PERM_MMAP (1<<0)
+#define SNDRV_PCM_PERM_MMAP_STATUS (1<<1)
+#define SNDRV_PCM_PERM_MMAP_CONTROL (1<<2)
+#define SNDRV_PCM_PERM_RW (1<<3)
+#define SNDRV_PCM_PERM_CONTROL (1<<4)
+#define SNDRV_PCM_PERM_STATUS (1<<5)
+#define SNDRV_PCM_PERM_SYNC (1<<6)
+#define SNDRV_PCM_PERM_MASK ((SNDRV_PCM_PERM_SYNC<<1)-1)
+
#define SNDRV_PCM_IOCTL_PVERSION _IOR('A', 0x00, int)
#define SNDRV_PCM_IOCTL_INFO _IOR('A', 0x01, struct snd_pcm_info)
#define SNDRV_PCM_IOCTL_TSTAMP _IOW('A', 0x02, int)
@@ -2502,6 +2502,7 @@ static int snd_pcm_open_file(struct file *file,
return -ENOMEM;
}
pcm_file->substream = substream;
+ pcm_file->perm = ~0;
if (substream->ref_count == 1)
substream->pcm_release = pcm_release_private;
file->private_data = pcm_file;
@@ -2894,10 +2895,11 @@ static int snd_pcm_anonymous_dup(struct file *file,
int fd, err, perm, flags;
struct file *nfile;
struct snd_pcm *pcm = substream->pcm;
+ struct snd_pcm_file *pcm_file;
if (get_user(perm, arg))
return -EFAULT;
- if (perm < 0)
+ if (perm & ~SNDRV_PCM_PERM_MASK)
return -EPERM;
flags = file->f_flags & (O_ACCMODE | O_NONBLOCK);
flags |= O_APPEND | O_CLOEXEC;
@@ -2922,6 +2924,8 @@ static int snd_pcm_anonymous_dup(struct file *file,
err = snd_pcm_open_file(nfile, substream->pcm,
substream->stream, substream->number);
if (err >= 0) {
+ pcm_file = nfile->private_data;
+ pcm_file->perm = perm;
put_user(fd, arg);
return 0;
}
@@ -2933,6 +2937,73 @@ static int snd_pcm_anonymous_dup(struct file *file,
return err;
}
+static int snd_pcm_ioctl_check_perm(struct snd_pcm_file *pcm_file,
+ unsigned int cmd)
+{
+ if (pcm_file->perm == ~0)
+ return 1;
+ /*
+ * the setup, linking and anonymous dup is not allowed
+ * for the restricted file descriptors
+ */
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_PVERSION:
+ case SNDRV_PCM_IOCTL_INFO:
+ case SNDRV_PCM_IOCTL_USER_PVERSION:
+ case SNDRV_PCM_IOCTL_CHANNEL_INFO:
+ return 1;
+ }
+ if (pcm_file->perm & SNDRV_PCM_PERM_CONTROL) {
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_PREPARE:
+ case SNDRV_PCM_IOCTL_RESET:
+ case SNDRV_PCM_IOCTL_START:
+ case SNDRV_PCM_IOCTL_XRUN:
+ case SNDRV_PCM_IOCTL_RESUME:
+ case SNDRV_PCM_IOCTL_DRAIN:
+ case SNDRV_PCM_IOCTL_DROP:
+ case SNDRV_PCM_IOCTL_PAUSE:
+ return 1;
+ default:
+ break;
+ }
+ }
+ if (pcm_file->perm & SNDRV_PCM_PERM_STATUS) {
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_STATUS:
+ case SNDRV_PCM_IOCTL_STATUS_EXT:
+ case SNDRV_PCM_IOCTL_DELAY:
+ return 1;
+ default:
+ break;
+ }
+ }
+ if (pcm_file->perm & SNDRV_PCM_PERM_SYNC) {
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_HWSYNC:
+ case SNDRV_PCM_IOCTL_SYNC_PTR:
+ case SNDRV_PCM_IOCTL_REWIND:
+ case SNDRV_PCM_IOCTL_FORWARD:
+ return 1;
+ default:
+ break;
+ }
+ }
+ if (pcm_file->perm & SNDRV_PCM_PERM_RW) {
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
+ case SNDRV_PCM_IOCTL_READI_FRAMES:
+ case SNDRV_PCM_IOCTL_WRITEN_FRAMES:
+ case SNDRV_PCM_IOCTL_READN_FRAMES:
+ return 1;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
static int snd_pcm_common_ioctl(struct file *file,
struct snd_pcm_substream *substream,
unsigned int cmd, void __user *arg)
@@ -2947,6 +3018,9 @@ static int snd_pcm_common_ioctl(struct file *file,
if (res < 0)
return res;
+ if (!snd_pcm_ioctl_check_perm(pcm_file, cmd))
+ return -EPERM;
+
switch (cmd) {
case SNDRV_PCM_IOCTL_PVERSION:
return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
@@ -3108,6 +3182,8 @@ static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count,
substream = pcm_file->substream;
if (PCM_RUNTIME_CHECK(substream))
return -ENXIO;
+ if (!(pcm_file->perm & SNDRV_PCM_PERM_RW))
+ return -EPERM;
runtime = substream->runtime;
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
@@ -3132,6 +3208,8 @@ static ssize_t snd_pcm_write(struct file *file, const char __user *buf,
substream = pcm_file->substream;
if (PCM_RUNTIME_CHECK(substream))
return -ENXIO;
+ if (!(pcm_file->perm & SNDRV_PCM_PERM_RW))
+ return -EPERM;
runtime = substream->runtime;
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
@@ -3158,6 +3236,8 @@ static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to)
substream = pcm_file->substream;
if (PCM_RUNTIME_CHECK(substream))
return -ENXIO;
+ if (!(pcm_file->perm & SNDRV_PCM_PERM_RW))
+ return -EPERM;
runtime = substream->runtime;
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
@@ -3194,6 +3274,8 @@ static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from)
substream = pcm_file->substream;
if (PCM_RUNTIME_CHECK(substream))
return -ENXIO;
+ if (!(pcm_file->perm & SNDRV_PCM_PERM_RW))
+ return -EPERM;
runtime = substream->runtime;
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
@@ -3295,6 +3377,9 @@ static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file
struct vm_area_struct *area)
{
long size;
+ struct snd_pcm_file *pcm_file = file->private_data;
+ if (!(pcm_file->perm & SNDRV_PCM_PERM_MMAP_STATUS))
+ return -EPERM;
if (!(area->vm_flags & VM_READ))
return -EINVAL;
size = area->vm_end - area->vm_start;
@@ -3331,6 +3416,9 @@ static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file
struct vm_area_struct *area)
{
long size;
+ struct snd_pcm_file *pcm_file = file->private_data;
+ if (!(pcm_file->perm & SNDRV_PCM_PERM_MMAP_CONTROL))
+ return -EPERM;
if (!(area->vm_flags & VM_READ))
return -EINVAL;
size = area->vm_end - area->vm_start;
@@ -3505,11 +3593,14 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file,
struct vm_area_struct *area)
{
struct snd_pcm_runtime *runtime;
+ struct snd_pcm_file *pcm_file = file->private_data;
long size;
unsigned long offset;
size_t dma_bytes;
int err;
+ if (!(pcm_file->perm & SNDRV_PCM_PERM_MMAP))
+ return -EPERM;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (!(area->vm_flags & (VM_WRITE|VM_READ)))
return -EINVAL;