Commit 62e08243 authored by Mikulas Patocka's avatar Mikulas Patocka Committed by Mike Snitzer

dm ioctl: fix alignment of event number in the device list

The size of struct dm_name_list is different on 32-bit and 64-bit
kernels (so "(nl + 1)" differs between 32-bit and 64-bit kernels).

This mismatch caused some harmless difference in padding when using 32-bit
or 64-bit kernel. Commit 23d70c5e ("dm ioctl: report event number in
DM_LIST_DEVICES") added reporting event number in the output of
DM_LIST_DEVICES_CMD. This difference in padding makes it impossible for
userspace to determine the location of the event number (the location
would be different when running on 32-bit and 64-bit kernels).

Fix the padding by using offsetof(struct dm_name_list, name) instead of
sizeof(struct dm_name_list) to determine the location of entries.

Also, the ioctl version number is incremented to 37 so that userspace
can use the version number to determine that the event number is present
and correctly located.

In addition, a global event is now raised when a DM device is created,
removed, renamed or when table is swapped, so that the user can monitor
for device changes.
Reported-by: default avatarEugene Syromiatnikov <esyr@redhat.com>
Fixes: 23d70c5e ("dm ioctl: report event number in DM_LIST_DEVICES")
Cc: stable@vger.kernel.org # 4.13
Signed-off-by: default avatarMikulas Patocka <mpatocka@redhat.com>
Signed-off-by: default avatarMike Snitzer <snitzer@redhat.com>
parent e19b205b
...@@ -149,5 +149,6 @@ static inline bool dm_message_test_buffer_overflow(char *result, unsigned maxlen ...@@ -149,5 +149,6 @@ static inline bool dm_message_test_buffer_overflow(char *result, unsigned maxlen
extern atomic_t dm_global_event_nr; extern atomic_t dm_global_event_nr;
extern wait_queue_head_t dm_global_eventq; extern wait_queue_head_t dm_global_eventq;
void dm_issue_global_event(void);
#endif #endif
...@@ -477,9 +477,13 @@ static int remove_all(struct file *filp, struct dm_ioctl *param, size_t param_si ...@@ -477,9 +477,13 @@ static int remove_all(struct file *filp, struct dm_ioctl *param, size_t param_si
* Round up the ptr to an 8-byte boundary. * Round up the ptr to an 8-byte boundary.
*/ */
#define ALIGN_MASK 7 #define ALIGN_MASK 7
static inline size_t align_val(size_t val)
{
return (val + ALIGN_MASK) & ~ALIGN_MASK;
}
static inline void *align_ptr(void *ptr) static inline void *align_ptr(void *ptr)
{ {
return (void *) (((size_t) (ptr + ALIGN_MASK)) & ~ALIGN_MASK); return (void *)align_val((size_t)ptr);
} }
/* /*
...@@ -505,7 +509,7 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_ ...@@ -505,7 +509,7 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_
struct hash_cell *hc; struct hash_cell *hc;
size_t len, needed = 0; size_t len, needed = 0;
struct gendisk *disk; struct gendisk *disk;
struct dm_name_list *nl, *old_nl = NULL; struct dm_name_list *orig_nl, *nl, *old_nl = NULL;
uint32_t *event_nr; uint32_t *event_nr;
down_write(&_hash_lock); down_write(&_hash_lock);
...@@ -516,17 +520,15 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_ ...@@ -516,17 +520,15 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_
*/ */
for (i = 0; i < NUM_BUCKETS; i++) { for (i = 0; i < NUM_BUCKETS; i++) {
list_for_each_entry (hc, _name_buckets + i, name_list) { list_for_each_entry (hc, _name_buckets + i, name_list) {
needed += sizeof(struct dm_name_list); needed += align_val(offsetof(struct dm_name_list, name) + strlen(hc->name) + 1);
needed += strlen(hc->name) + 1; needed += align_val(sizeof(uint32_t));
needed += ALIGN_MASK;
needed += (sizeof(uint32_t) + ALIGN_MASK) & ~ALIGN_MASK;
} }
} }
/* /*
* Grab our output buffer. * Grab our output buffer.
*/ */
nl = get_result_buffer(param, param_size, &len); nl = orig_nl = get_result_buffer(param, param_size, &len);
if (len < needed) { if (len < needed) {
param->flags |= DM_BUFFER_FULL_FLAG; param->flags |= DM_BUFFER_FULL_FLAG;
goto out; goto out;
...@@ -549,11 +551,16 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_ ...@@ -549,11 +551,16 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_
strcpy(nl->name, hc->name); strcpy(nl->name, hc->name);
old_nl = nl; old_nl = nl;
event_nr = align_ptr(((void *) (nl + 1)) + strlen(hc->name) + 1); event_nr = align_ptr(nl->name + strlen(hc->name) + 1);
*event_nr = dm_get_event_nr(hc->md); *event_nr = dm_get_event_nr(hc->md);
nl = align_ptr(event_nr + 1); nl = align_ptr(event_nr + 1);
} }
} }
/*
* If mismatch happens, security may be compromised due to buffer
* overflow, so it's better to crash.
*/
BUG_ON((char *)nl - (char *)orig_nl != needed);
out: out:
up_write(&_hash_lock); up_write(&_hash_lock);
...@@ -1621,7 +1628,8 @@ static int target_message(struct file *filp, struct dm_ioctl *param, size_t para ...@@ -1621,7 +1628,8 @@ static int target_message(struct file *filp, struct dm_ioctl *param, size_t para
* which has a variable size, is not used by the function processing * which has a variable size, is not used by the function processing
* the ioctl. * the ioctl.
*/ */
#define IOCTL_FLAGS_NO_PARAMS 1 #define IOCTL_FLAGS_NO_PARAMS 1
#define IOCTL_FLAGS_ISSUE_GLOBAL_EVENT 2
/*----------------------------------------------------------------- /*-----------------------------------------------------------------
* Implementation of open/close/ioctl on the special char * Implementation of open/close/ioctl on the special char
...@@ -1635,12 +1643,12 @@ static ioctl_fn lookup_ioctl(unsigned int cmd, int *ioctl_flags) ...@@ -1635,12 +1643,12 @@ static ioctl_fn lookup_ioctl(unsigned int cmd, int *ioctl_flags)
ioctl_fn fn; ioctl_fn fn;
} _ioctls[] = { } _ioctls[] = {
{DM_VERSION_CMD, 0, NULL}, /* version is dealt with elsewhere */ {DM_VERSION_CMD, 0, NULL}, /* version is dealt with elsewhere */
{DM_REMOVE_ALL_CMD, IOCTL_FLAGS_NO_PARAMS, remove_all}, {DM_REMOVE_ALL_CMD, IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, remove_all},
{DM_LIST_DEVICES_CMD, 0, list_devices}, {DM_LIST_DEVICES_CMD, 0, list_devices},
{DM_DEV_CREATE_CMD, IOCTL_FLAGS_NO_PARAMS, dev_create}, {DM_DEV_CREATE_CMD, IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_create},
{DM_DEV_REMOVE_CMD, IOCTL_FLAGS_NO_PARAMS, dev_remove}, {DM_DEV_REMOVE_CMD, IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_remove},
{DM_DEV_RENAME_CMD, 0, dev_rename}, {DM_DEV_RENAME_CMD, IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_rename},
{DM_DEV_SUSPEND_CMD, IOCTL_FLAGS_NO_PARAMS, dev_suspend}, {DM_DEV_SUSPEND_CMD, IOCTL_FLAGS_NO_PARAMS, dev_suspend},
{DM_DEV_STATUS_CMD, IOCTL_FLAGS_NO_PARAMS, dev_status}, {DM_DEV_STATUS_CMD, IOCTL_FLAGS_NO_PARAMS, dev_status},
{DM_DEV_WAIT_CMD, 0, dev_wait}, {DM_DEV_WAIT_CMD, 0, dev_wait},
...@@ -1869,6 +1877,9 @@ static int ctl_ioctl(struct file *file, uint command, struct dm_ioctl __user *us ...@@ -1869,6 +1877,9 @@ static int ctl_ioctl(struct file *file, uint command, struct dm_ioctl __user *us
unlikely(ioctl_flags & IOCTL_FLAGS_NO_PARAMS)) unlikely(ioctl_flags & IOCTL_FLAGS_NO_PARAMS))
DMERR("ioctl %d tried to output some data but has IOCTL_FLAGS_NO_PARAMS set", cmd); DMERR("ioctl %d tried to output some data but has IOCTL_FLAGS_NO_PARAMS set", cmd);
if (!r && ioctl_flags & IOCTL_FLAGS_ISSUE_GLOBAL_EVENT)
dm_issue_global_event();
/* /*
* Copy the results back to userland. * Copy the results back to userland.
*/ */
......
...@@ -52,6 +52,12 @@ static struct workqueue_struct *deferred_remove_workqueue; ...@@ -52,6 +52,12 @@ static struct workqueue_struct *deferred_remove_workqueue;
atomic_t dm_global_event_nr = ATOMIC_INIT(0); atomic_t dm_global_event_nr = ATOMIC_INIT(0);
DECLARE_WAIT_QUEUE_HEAD(dm_global_eventq); DECLARE_WAIT_QUEUE_HEAD(dm_global_eventq);
void dm_issue_global_event(void)
{
atomic_inc(&dm_global_event_nr);
wake_up(&dm_global_eventq);
}
/* /*
* One of these is allocated per bio. * One of these is allocated per bio.
*/ */
...@@ -1865,9 +1871,8 @@ static void event_callback(void *context) ...@@ -1865,9 +1871,8 @@ static void event_callback(void *context)
dm_send_uevents(&uevents, &disk_to_dev(md->disk)->kobj); dm_send_uevents(&uevents, &disk_to_dev(md->disk)->kobj);
atomic_inc(&md->event_nr); atomic_inc(&md->event_nr);
atomic_inc(&dm_global_event_nr);
wake_up(&md->eventq); wake_up(&md->eventq);
wake_up(&dm_global_eventq); dm_issue_global_event();
} }
/* /*
...@@ -2283,6 +2288,7 @@ struct dm_table *dm_swap_table(struct mapped_device *md, struct dm_table *table) ...@@ -2283,6 +2288,7 @@ struct dm_table *dm_swap_table(struct mapped_device *md, struct dm_table *table)
} }
map = __bind(md, table, &limits); map = __bind(md, table, &limits);
dm_issue_global_event();
out: out:
mutex_unlock(&md->suspend_lock); mutex_unlock(&md->suspend_lock);
......
...@@ -269,9 +269,9 @@ enum { ...@@ -269,9 +269,9 @@ enum {
#define DM_DEV_SET_GEOMETRY _IOWR(DM_IOCTL, DM_DEV_SET_GEOMETRY_CMD, struct dm_ioctl) #define DM_DEV_SET_GEOMETRY _IOWR(DM_IOCTL, DM_DEV_SET_GEOMETRY_CMD, struct dm_ioctl)
#define DM_VERSION_MAJOR 4 #define DM_VERSION_MAJOR 4
#define DM_VERSION_MINOR 36 #define DM_VERSION_MINOR 37
#define DM_VERSION_PATCHLEVEL 0 #define DM_VERSION_PATCHLEVEL 0
#define DM_VERSION_EXTRA "-ioctl (2017-06-09)" #define DM_VERSION_EXTRA "-ioctl (2017-09-20)"
/* Status bits */ /* Status bits */
#define DM_READONLY_FLAG (1 << 0) /* In/Out */ #define DM_READONLY_FLAG (1 << 0) /* In/Out */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment