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 | MODULE_PARM_DESC(dm_op_buf_max_size, | 56 | MODULE_PARM_DESC(dm_op_buf_max_size, |
| 57 | "Maximum size of a dm_op hypercall buffer"); | 57 | "Maximum size of a dm_op hypercall buffer"); |
| 58 | 58 | ||
| 59 | struct privcmd_data { | ||
| 60 | domid_t domid; | ||
| 61 | }; | ||
| 62 | |||
| 59 | static int privcmd_vma_range_is_mapped( | 63 | static int privcmd_vma_range_is_mapped( |
| 60 | struct vm_area_struct *vma, | 64 | struct vm_area_struct *vma, |
| 61 | unsigned long addr, | 65 | unsigned long addr, |
| 62 | unsigned long nr_pages); | 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 | struct privcmd_hypercall hypercall; | 71 | struct privcmd_hypercall hypercall; |
| 67 | long ret; | 72 | long ret; |
| 68 | 73 | ||
| 74 | /* Disallow arbitrary hypercalls if restricted */ | ||
| 75 | if (data->domid != DOMID_INVALID) | ||
| 76 | return -EPERM; | ||
| 77 | |||
| 69 | if (copy_from_user(&hypercall, udata, sizeof(hypercall))) | 78 | if (copy_from_user(&hypercall, udata, sizeof(hypercall))) |
| 70 | return -EFAULT; | 79 | return -EFAULT; |
| 71 | 80 | ||
| @@ -242,8 +251,9 @@ static int mmap_gfn_range(void *data, void *state) · 170 unmodified lines | |||
| 242 | return 0; | 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 | struct privcmd_mmap mmapcmd; | 257 | struct privcmd_mmap mmapcmd; |
| 248 | struct mm_struct *mm = current->mm; | 258 | struct mm_struct *mm = current->mm; |
| 249 | struct vm_area_struct *vma; | 259 | struct vm_area_struct *vma; |
| @@ -258,6 +268,10 @@ static long privcmd_ioctl_mmap(void __user *udata) · 8 unmodified lines | |||
| 258 | if (copy_from_user(&mmapcmd, udata, sizeof(mmapcmd))) | 268 | if (copy_from_user(&mmapcmd, udata, sizeof(mmapcmd))) |
| 259 | return -EFAULT; | 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 | rc = gather_array(&pagelist, | 275 | rc = gather_array(&pagelist, |
| 262 | mmapcmd.num, sizeof(struct privcmd_mmap_entry), | 276 | mmapcmd.num, sizeof(struct privcmd_mmap_entry), |
| 263 | mmapcmd.entry); | 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 | static const struct vm_operations_struct privcmd_vm_ops; | 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 | int ret; | 450 | int ret; |
| 435 | struct privcmd_mmapbatch_v2 m; | 451 | struct privcmd_mmapbatch_v2 m; |
| 436 | struct mm_struct *mm = current->mm; | 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 | return -EINVAL; | 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 | nr_pages = DIV_ROUND_UP(m.num, XEN_PFN_PER_PAGE); | 482 | nr_pages = DIV_ROUND_UP(m.num, XEN_PFN_PER_PAGE); |
| 463 | if ((m.num <= 0) || (nr_pages > (LONG_MAX >> PAGE_SHIFT))) | 483 | if ((m.num <= 0) || (nr_pages > (LONG_MAX >> PAGE_SHIFT))) |
| 464 | return -EINVAL; | 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 | struct privcmd_dm_op kdata; | 629 | struct privcmd_dm_op kdata; |
| 609 | struct privcmd_dm_op_buf *kbufs; | 630 | struct privcmd_dm_op_buf *kbufs; |
| 610 | unsigned int nr_pages = 0; | 631 | unsigned int nr_pages = 0; |
| @@ -616,6 +637,10 @@ static long privcmd_ioctl_dm_op(void __user *udata) · 5 unmodified lines | |||
| 616 | if (copy_from_user(&kdata, udata, sizeof(kdata))) | 637 | if (copy_from_user(&kdata, udata, sizeof(kdata))) |
| 617 | return -EFAULT; | 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 | if (kdata.num == 0) | 644 | if (kdata.num == 0) |
| 620 | return 0; | 645 | return 0; |
| 621 | 646 | ||
| @@ -683,6 +708,23 @@ out: · 61 unmodified lines | |||
| 683 | return rc; | 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 | static long privcmd_ioctl(struct file *file, | 728 | static long privcmd_ioctl(struct file *file, |
| 687 | unsigned int cmd, unsigned long data) | 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 | switch (cmd) { | 734 | switch (cmd) { |
| 693 | case IOCTL_PRIVCMD_HYPERCALL: | 735 | case IOCTL_PRIVCMD_HYPERCALL: |
| 694 | ret = privcmd_ioctl_hypercall(udata); | 736 | ret = privcmd_ioctl_hypercall(file, udata); |
| 695 | break; | 737 | break; |
| 696 | 738 | ||
| 697 | case IOCTL_PRIVCMD_MMAP: | 739 | case IOCTL_PRIVCMD_MMAP: |
| 698 | ret = privcmd_ioctl_mmap(udata); | 740 | ret = privcmd_ioctl_mmap(file, udata); |
| 699 | break; | 741 | break; |
| 700 | 742 | ||
| 701 | case IOCTL_PRIVCMD_MMAPBATCH: | 743 | case IOCTL_PRIVCMD_MMAPBATCH: |
| 702 | ret = privcmd_ioctl_mmap_batch(udata, 1); | 744 | ret = privcmd_ioctl_mmap_batch(file, udata, 1); |
| 703 | break; | 745 | break; |
| 704 | 746 | ||
| 705 | case IOCTL_PRIVCMD_MMAPBATCH_V2: | 747 | case IOCTL_PRIVCMD_MMAPBATCH_V2: |
| 706 | ret = privcmd_ioctl_mmap_batch(udata, 2); | 748 | ret = privcmd_ioctl_mmap_batch(file, udata, 2); |
| 707 | break; | 749 | break; |
| 708 | 750 | ||
| 709 | case IOCTL_PRIVCMD_DM_OP: | 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 | break; | 757 | break; |
| 712 | 758 | ||
| 713 | default: | 759 | default: |
| @@ -717,6 +763,28 @@ static long privcmd_ioctl(struct file *file, · 3 unmodified lines | |||
| 717 | return ret; | 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 | static void privcmd_close(struct vm_area_struct *vma) | 788 | static void privcmd_close(struct vm_area_struct *vma) |
| 721 | { | 789 | { |
| 722 | struct page **pages = vma->vm_private_data; | 790 | struct page **pages = vma->vm_private_data; |
| @@ -785,6 +853,8 @@ static int privcmd_vma_range_is_mapped( · 62 unmodified lines | |||
| 785 | const struct file_operations xen_privcmd_fops = { | 853 | const struct file_operations xen_privcmd_fops = { |
| 786 | .owner = THIS_MODULE, | 854 | .owner = THIS_MODULE, |
| 787 | .unlocked_ioctl = privcmd_ioctl, | 855 | .unlocked_ioctl = privcmd_ioctl, |
| 856 | .open = privcmd_open, | ||
| 857 | .release = privcmd_release, | ||
| 788 | .mmap = privcmd_mmap, | 858 | .mmap = privcmd_mmap, |
| 789 | }; | 859 | }; |
| 790 | EXPORT_SYMBOL_GPL(xen_privcmd_fops); | 860 | EXPORT_SYMBOL_GPL(xen_privcmd_fops); |
| @@ -111,5 +111,7 @@ struct privcmd_dm_op { · 110 unmodified lines | |||
| 111 | _IOC(_IOC_NONE, 'P', 4, sizeof(struct privcmd_mmapbatch_v2)) | 111 | _IOC(_IOC_NONE, 'P', 4, sizeof(struct privcmd_mmapbatch_v2)) |
| 112 | #define IOCTL_PRIVCMD_DM_OP \ | 112 | #define IOCTL_PRIVCMD_DM_OP \ |
| 113 | _IOC(_IOC_NONE, 'P', 5, sizeof(struct privcmd_dm_op)) | 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 | #endif /* __LINUX_PUBLIC_PRIVCMD_H__ */ | 117 | #endif /* __LINUX_PUBLIC_PRIVCMD_H__ */ |