diff options
author | Wenwen Wang <wang6495@umn.edu> | 2018-10-03 12:43:59 -0400 |
---|---|---|
committer | Mike Snitzer <snitzer@redhat.com> | 2018-10-18 11:54:07 -0400 |
commit | 800a7340ab7dd667edf95e74d8e4f23a17e87076 (patch) | |
tree | 661f1c6576b6862811a74b5431500c1487943b2f | |
parent | bab5d988841e58fec6ae22f486905ddde2d715f4 (diff) |
dm ioctl: harden copy_params()'s copy_from_user() from malicious users
In copy_params(), the struct 'dm_ioctl' is first copied from the user
space buffer 'user' to 'param_kernel' and the field 'data_size' is
checked against 'minimum_data_size' (size of 'struct dm_ioctl' payload
up to its 'data' member). If the check fails, an error code EINVAL will be
returned. Otherwise, param_kernel->data_size is used to do a second copy,
which copies from the same user-space buffer to 'dmi'. After the second
copy, only 'dmi->data_size' is checked against 'param_kernel->data_size'.
Given that the buffer 'user' resides in the user space, a malicious
user-space process can race to change the content in the buffer between
the two copies. This way, the attacker can inject inconsistent data
into 'dmi' (versus previously validated 'param_kernel').
Fix redundant copying of 'minimum_data_size' from user-space buffer by
using the first copy stored in 'param_kernel'. Also remove the
'data_size' check after the second copy because it is now unnecessary.
Cc: stable@vger.kernel.org
Signed-off-by: Wenwen Wang <wang6495@umn.edu>
Signed-off-by: Mike Snitzer <snitzer@redhat.com>
-rw-r--r-- | drivers/md/dm-ioctl.c | 18 |
1 files changed, 6 insertions, 12 deletions
diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index b810ea77e6b1..f666778ad237 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c | |||
@@ -1720,8 +1720,7 @@ static void free_params(struct dm_ioctl *param, size_t param_size, int param_fla | |||
1720 | } | 1720 | } |
1721 | 1721 | ||
1722 | static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl *param_kernel, | 1722 | static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl *param_kernel, |
1723 | int ioctl_flags, | 1723 | int ioctl_flags, struct dm_ioctl **param, int *param_flags) |
1724 | struct dm_ioctl **param, int *param_flags) | ||
1725 | { | 1724 | { |
1726 | struct dm_ioctl *dmi; | 1725 | struct dm_ioctl *dmi; |
1727 | int secure_data; | 1726 | int secure_data; |
@@ -1762,18 +1761,13 @@ static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl *param_kern | |||
1762 | 1761 | ||
1763 | *param_flags |= DM_PARAMS_MALLOC; | 1762 | *param_flags |= DM_PARAMS_MALLOC; |
1764 | 1763 | ||
1765 | if (copy_from_user(dmi, user, param_kernel->data_size)) | 1764 | /* Copy from param_kernel (which was already copied from user) */ |
1766 | goto bad; | 1765 | memcpy(dmi, param_kernel, minimum_data_size); |
1767 | 1766 | ||
1768 | data_copied: | 1767 | if (copy_from_user(&dmi->data, (char __user *)user + minimum_data_size, |
1769 | /* | 1768 | param_kernel->data_size - minimum_data_size)) |
1770 | * Abort if something changed the ioctl data while it was being copied. | ||
1771 | */ | ||
1772 | if (dmi->data_size != param_kernel->data_size) { | ||
1773 | DMERR("rejecting ioctl: data size modified while processing parameters"); | ||
1774 | goto bad; | 1769 | goto bad; |
1775 | } | 1770 | data_copied: |
1776 | |||
1777 | /* Wipe the user buffer so we do not return it to userspace */ | 1771 | /* Wipe the user buffer so we do not return it to userspace */ |
1778 | if (secure_data && clear_user(user, param_kernel->data_size)) | 1772 | if (secure_data && clear_user(user, param_kernel->data_size)) |
1779 | goto bad; | 1773 | goto bad; |