dm: __list_versions(): Only process targets once

Instead of doing a prescan to determine the length of buffer required,
checking the supplied buffer is big enough, and then doing a second
scan to fill the output buffer just do a single scan and detect when
the buffer is too short.

This removes any problems that might occur if a 'target' is added
between the scans.

For additional safety only call strlen(tt->name) once and use the
returned length for everything (incuding the copy).
Ensure than all the pad bytes between the entries are zero.

Set param->data_size to the actual size of the data.
It was slightly large because ALIGN_MASK was added in instead of the size
being rounded up.

Signed-off-by: David Laight <david.laight.linux@gmail.com>
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
This commit is contained in:
David Laight 2026-06-24 15:52:41 +01:00 committed by Mikulas Patocka
parent c92d632f86
commit 78726ba0ff

View File

@ -54,10 +54,8 @@ struct hash_cell {
};
struct vers_iter {
size_t param_size;
struct dm_target_versions *vers, *old_vers;
char *end;
uint32_t flags;
};
@ -771,41 +769,38 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_
return 0;
}
static void list_version_get_needed(struct target_type *tt, void *needed_param)
{
size_t *needed = needed_param;
*needed += sizeof(struct dm_target_versions);
*needed += strlen(tt->name) + 1;
*needed += ALIGN_MASK;
}
static void list_version_get_info(struct target_type *tt, void *param)
{
struct vers_iter *info = param;
struct dm_target_versions *vers = info->vers;
size_t name_len = strlen(tt->name);
/* Check space - it might have changed since the first iteration */
if ((char *)info->vers + sizeof(struct dm_target_versions) + strlen(tt->name) + 1 > info->end) {
info->flags = DM_BUFFER_FULL_FLAG;
if (!vers)
return;
info->old_vers = vers;
info->vers = align_ptr((void *)(info->vers + 1) + name_len + 1);
/* Check space */
if ((char *)info->vers > info->end) {
info->vers = NULL;
return;
}
if (info->old_vers)
info->old_vers->next = (uint32_t) ((void *)info->vers - (void *)info->old_vers);
/* Zero padding and terminate vers->name[] */
((u64 *)info->vers)[-1] = 0;
info->vers->version[0] = tt->version[0];
info->vers->version[1] = tt->version[1];
info->vers->version[2] = tt->version[2];
info->vers->next = 0;
strcpy(info->vers->name, tt->name);
vers->next = (char *)info->vers - (char *)vers;
info->old_vers = info->vers;
info->vers = align_ptr((void *)(info->vers + 1) + strlen(tt->name) + 1);
vers->version[0] = tt->version[0];
vers->version[1] = tt->version[1];
vers->version[2] = tt->version[2];
memcpy(vers->name, tt->name, name_len);
}
static int __list_versions(struct dm_ioctl *param, size_t param_size, const char *name)
{
size_t len, needed = 0;
size_t len;
struct dm_target_versions *vers;
struct vers_iter iter_info;
struct target_type *tt = NULL;
@ -816,41 +811,31 @@ static int __list_versions(struct dm_ioctl *param, size_t param_size, const char
return -EINVAL;
}
/*
* Loop through all the devices working out how much
* space we need.
*/
if (!tt)
dm_target_iterate(list_version_get_needed, &needed);
else
list_version_get_needed(tt, &needed);
/*
* Grab our output buffer.
*/
vers = get_result_buffer(param, param_size, &len);
if (len < needed) {
param->flags |= DM_BUFFER_FULL_FLAG;
goto out;
}
param->data_size = param->data_start + needed;
iter_info.param_size = param_size;
iter_info.old_vers = NULL;
iter_info.vers = vers;
iter_info.flags = 0;
iter_info.end = (char *)vers + needed;
iter_info.end = (char *)vers + len;
/*
* Now loop through filling out the names & versions.
* Loop through filling out the names & versions.
*/
if (!tt)
dm_target_iterate(list_version_get_info, &iter_info);
else
list_version_get_info(tt, &iter_info);
param->flags |= iter_info.flags;
out:
if (iter_info.vers) {
if (iter_info.old_vers)
iter_info.old_vers->next = 0;
param->data_size = param->data_start + ((char *)iter_info.vers - (char *)vers);
} else {
param->flags |= DM_BUFFER_FULL_FLAG;
}
if (tt)
dm_put_target_type(tt);
return 0;