The purpose if this ioctl is to allow a user of privcmd to restrict its operation such that it will no longer service arbitrary hypercalls via IOCTL_PRIVCMD_HYPERCALL, and will check for a matching domid when servicing IOCTL_PRIVCMD_DM_OP or IOCTL_PRIVCMD_MMAP*. The aim of this is to limit the attack surface for a compromised device model.
Unified · Split
| @@ -56,16 +56,25 @@ module_param_named(dm_op_buf_max_size, privcmd_dm_op_buf_max_size, uint, · 55 unmodified lines | ||
| 56 | 56 | MODULE_PARM_DESC(dm_op_buf_max_size, |
| 57 | 57 | "Maximum size of a dm_op hypercall buffer"); |
| 58 | 58 | |
| 59 | +struct privcmd_data { | |
| 60 | + domid_t domid; | |
| 61 | +}; | |
| 62 | + | |
| 59 | 63 | static int privcmd_vma_range_is_mapped( |
| 60 | 64 | struct vm_area_struct *vma, |
| 61 | 65 | unsigned long addr, |
| 62 | 66 | unsigned long nr_pages); |
| 63 | 67 | |
| 64 | −static long privcmd_ioctl_hypercall(void __user *udata) | |
| 68 | +static long privcmd_ioctl_hypercall(struct file *file, void __user *udata) | |
| 65 | 69 | { |
| 70 | + struct privcmd_data *data = file->private_data; | |
| 66 | 71 | struct privcmd_hypercall hypercall; |
| 67 | 72 | long ret; |
| 68 | 73 | |
| 74 | + /* Disallow arbitrary hypercalls if restricted */ | |
| 75 | + if (data->domid != DOMID_INVALID) | |
| 76 | + return -EPERM; | |
| 77 | + | |
| 69 | 78 | if (copy_from_user(&hypercall, udata, sizeof(hypercall))) |
| 70 | 79 | return -EFAULT; |
| 71 | 80 | |
| @@ -242,8 +251,9 @@ static int mmap_gfn_range(void *data, void *state) · 170 unmodified lines | ||
| 242 | 251 | return 0; |
| 243 | 252 | } |
| 244 | 253 | |
| 245 | −static long privcmd_ioctl_mmap(void __user *udata) | |
| 254 | +static long privcmd_ioctl_mmap(struct file *file, void __user *udata) | |
| 246 | 255 | { |
| 256 | + struct privcmd_data *data = file->private_data; | |
| 247 | 257 | struct privcmd_mmap mmapcmd; |
| 248 | 258 | struct mm_struct *mm = current->mm; |
| 249 | 259 | struct vm_area_struct *vma; |
| @@ -258,6 +268,10 @@ static long privcmd_ioctl_mmap(void __user *udata) · 8 unmodified lines | ||
| 258 | 268 | if (copy_from_user(&mmapcmd, udata, sizeof(mmapcmd))) |
| 259 | 269 | return -EFAULT; |
| 260 | 270 | |
| 271 | + /* If restriction is in place, check the domid matches */ | |
| 272 | + if (data->domid != DOMID_INVALID && data->domid != mmapcmd.dom) | |
| 273 | + return -EPERM; | |
| 274 | + | |
| 261 | 275 | rc = gather_array(&pagelist, |
| 262 | 276 | mmapcmd.num, sizeof(struct privcmd_mmap_entry), |
| 263 | 277 | mmapcmd.entry); |
| @@ -429,8 +443,10 @@ static int alloc_empty_pages(struct vm_area_struct *vma, int numpgs) · 165 unmodified lines | ||
| 429 | 443 | |
| 430 | 444 | static const struct vm_operations_struct privcmd_vm_ops; |
| 431 | 445 | |
| 432 | −static long privcmd_ioctl_mmap_batch(void __user *udata, int version) | |
| 446 | +static long privcmd_ioctl_mmap_batch( | |
| 447 | + struct file *file, void __user *udata, int version) | |
| 433 | 448 | { |
| 449 | + struct privcmd_data *data = file->private_data; | |
| 434 | 450 | int ret; |
| 435 | 451 | struct privcmd_mmapbatch_v2 m; |
| 436 | 452 | struct mm_struct *mm = current->mm; |
| @@ -459,6 +475,10 @@ static long privcmd_ioctl_mmap_batch(void __user *udata, int version) · 22 unmodified lines | ||
| 459 | 475 | return -EINVAL; |
| 460 | 476 | } |
| 461 | 477 | |
| 478 | + /* If restriction is in place, check the domid matches */ | |
| 479 | + if (data->domid != DOMID_INVALID && data->domid != m.dom) | |
| 480 | + return -EPERM; | |
| 481 | + | |
| 462 | 482 | nr_pages = DIV_ROUND_UP(m.num, XEN_PFN_PER_PAGE); |
| 463 | 483 | if ((m.num <= 0) || (nr_pages > (LONG_MAX >> PAGE_SHIFT))) |
| 464 | 484 | return -EINVAL; |
| @@ -603,8 +623,9 @@ static void unlock_pages(struct page *pages[], unsigned int nr_pages) · 138 unmodified lines | ||
| 603 | 623 | } |
| 604 | 624 | } |
| 605 | 625 | |
| 606 | −static long privcmd_ioctl_dm_op(void __user *udata) | |
| 626 | +static long privcmd_ioctl_dm_op(struct file *file, void __user *udata) | |
| 607 | 627 | { |
| 628 | + struct privcmd_data *data = file->private_data; | |
| 608 | 629 | struct privcmd_dm_op kdata; |
| 609 | 630 | struct privcmd_dm_op_buf *kbufs; |
| 610 | 631 | unsigned int nr_pages = 0; |
| @@ -616,6 +637,10 @@ static long privcmd_ioctl_dm_op(void __user *udata) · 5 unmodified lines | ||
| 616 | 637 | if (copy_from_user(&kdata, udata, sizeof(kdata))) |
| 617 | 638 | return -EFAULT; |
| 618 | 639 | |
| 640 | + /* If restriction is in place, check the domid matches */ | |
| 641 | + if (data->domid != DOMID_INVALID && data->domid != kdata.dom) | |
| 642 | + return -EPERM; | |
| 643 | + | |
| 619 | 644 | if (kdata.num == 0) |
| 620 | 645 | return 0; |
| 621 | 646 | |
| @@ -683,6 +708,23 @@ out: · 61 unmodified lines | ||
| 683 | 708 | return rc; |
| 684 | 709 | } |
| 685 | 710 | |
| 711 | +static long privcmd_ioctl_restrict(struct file *file, void __user *udata) | |
| 712 | +{ | |
| 713 | + struct privcmd_data *data = file->private_data; | |
| 714 | + domid_t dom; | |
| 715 | + | |
| 716 | + if (copy_from_user(&dom, udata, sizeof(dom))) | |
| 717 | + return -EFAULT; | |
| 718 | + | |
| 719 | + /* Set restriction to the specified domain, or check it matches */ | |
| 720 | + if (data->domid == DOMID_INVALID) | |
| 721 | + data->domid = dom; | |
| 722 | + else if (data->domid != dom) | |
| 723 | + return -EINVAL; | |
| 724 | + | |
| 725 | + return 0; | |
| 726 | +} | |
| 727 | + | |
| 686 | 728 | static long privcmd_ioctl(struct file *file, |
| 687 | 729 | unsigned int cmd, unsigned long data) |
| 688 | 730 | { |
| @@ -691,23 +733,27 @@ static long privcmd_ioctl(struct file *file, · 2 unmodified lines | ||
| 691 | 733 | |
| 692 | 734 | switch (cmd) { |
| 693 | 735 | case IOCTL_PRIVCMD_HYPERCALL: |
| 694 | − ret = privcmd_ioctl_hypercall(udata); | |
| 736 | + ret = privcmd_ioctl_hypercall(file, udata); | |
| 695 | 737 | break; |
| 696 | 738 | |
| 697 | 739 | case IOCTL_PRIVCMD_MMAP: |
| 698 | − ret = privcmd_ioctl_mmap(udata); | |
| 740 | + ret = privcmd_ioctl_mmap(file, udata); | |
| 699 | 741 | break; |
| 700 | 742 | |
| 701 | 743 | case IOCTL_PRIVCMD_MMAPBATCH: |
| 702 | − ret = privcmd_ioctl_mmap_batch(udata, 1); | |
| 744 | + ret = privcmd_ioctl_mmap_batch(file, udata, 1); | |
| 703 | 745 | break; |
| 704 | 746 | |
| 705 | 747 | case IOCTL_PRIVCMD_MMAPBATCH_V2: |
| 706 | − ret = privcmd_ioctl_mmap_batch(udata, 2); | |
| 748 | + ret = privcmd_ioctl_mmap_batch(file, udata, 2); | |
| 707 | 749 | break; |
| 708 | 750 | |
| 709 | 751 | case IOCTL_PRIVCMD_DM_OP: |
| 710 | − ret = privcmd_ioctl_dm_op(udata); | |
| 752 | + ret = privcmd_ioctl_dm_op(file, udata); | |
| 753 | + break; | |
| 754 | + | |
| 755 | + case IOCTL_PRIVCMD_RESTRICT: | |
| 756 | + ret = privcmd_ioctl_restrict(file, udata); | |
| 711 | 757 | break; |
| 712 | 758 | |
| 713 | 759 | default: |
| @@ -717,6 +763,28 @@ static long privcmd_ioctl(struct file *file, · 3 unmodified lines | ||
| 717 | 763 | return ret; |
| 718 | 764 | } |
| 719 | 765 | |
| 766 | +static int privcmd_open(struct inode *ino, struct file *file) | |
| 767 | +{ | |
| 768 | + struct privcmd_data *data = kzalloc(sizeof(*data), GFP_KERNEL); | |
| 769 | + | |
| 770 | + if (!data) | |
| 771 | + return -ENOMEM; | |
| 772 | + | |
| 773 | + /* DOMID_INVALID implies no restriction */ | |
| 774 | + data->domid = DOMID_INVALID; | |
| 775 | + | |
| 776 | + file->private_data = data; | |
| 777 | + return 0; | |
| 778 | +} | |
| 779 | + | |
| 780 | +static int privcmd_release(struct inode *ino, struct file *file) | |
| 781 | +{ | |
| 782 | + struct privcmd_data *data = file->private_data; | |
| 783 | + | |
| 784 | + kfree(data); | |
| 785 | + return 0; | |
| 786 | +} | |
| 787 | + | |
| 720 | 788 | static void privcmd_close(struct vm_area_struct *vma) |
| 721 | 789 | { |
| 722 | 790 | struct page **pages = vma->vm_private_data; |
| @@ -785,6 +853,8 @@ static int privcmd_vma_range_is_mapped( · 62 unmodified lines | ||
| 785 | 853 | const struct file_operations xen_privcmd_fops = { |
| 786 | 854 | .owner = THIS_MODULE, |
| 787 | 855 | .unlocked_ioctl = privcmd_ioctl, |
| 856 | + .open = privcmd_open, | |
| 857 | + .release = privcmd_release, | |
| 788 | 858 | .mmap = privcmd_mmap, |
| 789 | 859 | }; |
| 790 | 860 | EXPORT_SYMBOL_GPL(xen_privcmd_fops); |
| @@ -111,5 +111,7 @@ struct privcmd_dm_op { · 110 unmodified lines | ||
| 111 | 111 | _IOC(_IOC_NONE, 'P', 4, sizeof(struct privcmd_mmapbatch_v2)) |
| 112 | 112 | #define IOCTL_PRIVCMD_DM_OP \ |
| 113 | 113 | _IOC(_IOC_NONE, 'P', 5, sizeof(struct privcmd_dm_op)) |
| 114 | +#define IOCTL_PRIVCMD_RESTRICT \ | |
| 115 | + _IOC(_IOC_NONE, 'P', 6, sizeof(domid_t)) | |
| 114 | 116 | |
| 115 | 117 | #endif /* __LINUX_PUBLIC_PRIVCMD_H__ */ |