diff --git a/0001-RHEL9.patch b/0001-RHEL9.patch index 55b6150..af33f96 100644 --- a/0001-RHEL9.patch +++ b/0001-RHEL9.patch @@ -15,14 +15,14 @@ index 2d35b7660..4993d445a 100644 +++ b/VERSION @@ -1 +1 @@ -2.03.33(2) (2025-06-27) -+2.03.33(2)-RHEL9 (2025-09-25) ++2.03.33(2)-RHEL9 (2026-06-04) diff --git a/VERSION_DM b/VERSION_DM index 0e8dcd346..c0564e0ea 100644 --- a/VERSION_DM +++ b/VERSION_DM @@ -1 +1 @@ -1.02.207 (2025-06-27) -+1.02.207-RHEL9 (2025-09-25) ++1.02.207-RHEL9 (2026-06-04) -- 2.51.0 diff --git a/0062-cov-pvck-fix-TOCTOU-race-in-get_devicefile.patch b/0062-cov-pvck-fix-TOCTOU-race-in-get_devicefile.patch new file mode 100644 index 0000000..46de84f --- /dev/null +++ b/0062-cov-pvck-fix-TOCTOU-race-in-get_devicefile.patch @@ -0,0 +1,61 @@ +From 442f1cff0766899544dbdb6a7ae88166ef7016e5 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 21:52:58 +0200 +Subject: [PATCH 062/211] cov: pvck: fix TOCTOU race in get_devicefile + +Remove stat() check before open() to eliminate TOCTOU race. +Instead, open the file first, then use fstat() on the fd to +verify it's a regular file. Close fd on all error paths. + +This ensures we're checking the actual file we opened, not +a path that could have changed between stat() and open(). + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 2079fc370d05b174f43a501617acba89e86240f4) +--- + tools/pvck.c | 20 ++++++++++++++------ + 1 file changed, 14 insertions(+), 6 deletions(-) + +diff --git a/tools/pvck.c b/tools/pvck.c +index 71171092c..8c7bbc92f 100644 +--- a/tools/pvck.c ++++ b/tools/pvck.c +@@ -352,21 +352,29 @@ static struct devicefile *get_devicefile(struct cmd_context *cmd, const char *pa + struct stat sb; + struct devicefile *def; + size_t len; ++ int fd; ++ ++ if ((fd = open(path, O_RDONLY)) < 0) ++ return_NULL; + +- if (stat(path, &sb)) ++ if (fstat(fd, &sb) < 0) { ++ (void) close(fd); + return_NULL; ++ } + +- if ((sb.st_mode & S_IFMT) != S_IFREG) ++ if ((sb.st_mode & S_IFMT) != S_IFREG) { ++ (void) close(fd); + return_NULL; ++ } + + len = strlen(path) + 1; +- if (!(def = dm_pool_alloc(cmd->mem, sizeof(struct devicefile) + len))) ++ if (!(def = dm_pool_alloc(cmd->mem, sizeof(struct devicefile) + len))) { ++ (void) close(fd); + return_NULL; ++ } + + memcpy(def->path, path, len); +- +- if ((def->fd = open(path, O_RDONLY)) < 0) +- return_NULL; ++ def->fd = fd; + + return def; + } +-- +2.54.0 + diff --git a/0063-cov-libdm-fix-TOCTOU-race-in-_sysfs_find_kernel_name.patch b/0063-cov-libdm-fix-TOCTOU-race-in-_sysfs_find_kernel_name.patch new file mode 100644 index 0000000..eb08b60 --- /dev/null +++ b/0063-cov-libdm-fix-TOCTOU-race-in-_sysfs_find_kernel_name.patch @@ -0,0 +1,129 @@ +From 0c5fb9c14a75042620ddd43de513f1a1119e60be Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 21:37:54 +0200 +Subject: [PATCH 063/211] cov: libdm: fix TOCTOU race in + _sysfs_find_kernel_name + +Remove stat() check before opendir() to eliminate TOCTOU race. +Instead, call opendir() directly and check errno: +- ENOTDIR: silently skip (path is not a directory) +- Other errors: log with log_sys_debug() + +This simplifies the code and eliminates the race condition. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit f2f410e28db30a37e70fb7b2920ea1bd95d96d15) +--- + libdm/libdm-common.c | 80 +++++++++++++++++++++----------------------- + 1 file changed, 38 insertions(+), 42 deletions(-) + +diff --git a/libdm/libdm-common.c b/libdm/libdm-common.c +index e4fdbbf05..21bdd826e 100644 +--- a/libdm/libdm-common.c ++++ b/libdm/libdm-common.c +@@ -1906,7 +1906,6 @@ static int _sysfs_find_kernel_name(uint32_t major, uint32_t minor, char *buf, si + char path[PATH_MAX]; + struct dirent *dirent, *dirent_dev; + DIR *d, *d_dev; +- struct stat st; + int r = 0, sz; + + if (!*_sysfs_dir || +@@ -1938,56 +1937,53 @@ static int _sysfs_find_kernel_name(uint32_t major, uint32_t minor, char *buf, si + } + + path[sz - 4] = 0; /* strip /dev from end of path string */ +- if (stat(path, &st)) ++ ++ /* let's assume there is no tree-complex device in past systems */ ++ if (!(d_dev = opendir(path))) { ++ /* Silently skip non-directories, log other errors */ ++ if (errno != ENOTDIR) ++ log_sys_debug("opendir", path); + continue; ++ } + +- if (S_ISDIR(st.st_mode)) { ++ while ((dirent_dev = readdir(d_dev))) { ++ name_dev = dirent_dev->d_name; ++ ++ /* skip known ignorable paths */ ++ if (!strcmp(name_dev, ".") || !strcmp(name_dev, "..") || ++ !strcmp(name_dev, "bdi") || ++ !strcmp(name_dev, "dev") || ++ !strcmp(name_dev, "device") || ++ !strcmp(name_dev, "holders") || ++ !strcmp(name_dev, "integrity") || ++ !strcmp(name_dev, "loop") || ++ !strcmp(name_dev, "queue") || ++ !strcmp(name_dev, "md") || ++ !strcmp(name_dev, "mq") || ++ !strcmp(name_dev, "power") || ++ !strcmp(name_dev, "removable") || ++ !strcmp(name_dev, "slave") || ++ !strcmp(name_dev, "slaves") || ++ !strcmp(name_dev, "subsystem") || ++ !strcmp(name_dev, "trace") || ++ !strcmp(name_dev, "uevent")) ++ continue; + +- /* let's assume there is no tree-complex device in past systems */ +- if (!(d_dev = opendir(path))) { +- log_sys_debug("opendir", path); ++ if (dm_snprintf(path, sizeof(path), "%sblock/%s/%s/dev", ++ _sysfs_dir, name, name_dev) == -1) { ++ log_warn("Couldn't create path for %s/%s.", name, name_dev); + continue; + } + +- while ((dirent_dev = readdir(d_dev))) { +- name_dev = dirent_dev->d_name; +- +- /* skip known ignorable paths */ +- if (!strcmp(name_dev, ".") || !strcmp(name_dev, "..") || +- !strcmp(name_dev, "bdi") || +- !strcmp(name_dev, "dev") || +- !strcmp(name_dev, "device") || +- !strcmp(name_dev, "holders") || +- !strcmp(name_dev, "integrity") || +- !strcmp(name_dev, "loop") || +- !strcmp(name_dev, "queue") || +- !strcmp(name_dev, "md") || +- !strcmp(name_dev, "mq") || +- !strcmp(name_dev, "power") || +- !strcmp(name_dev, "removable") || +- !strcmp(name_dev, "slave") || +- !strcmp(name_dev, "slaves") || +- !strcmp(name_dev, "subsystem") || +- !strcmp(name_dev, "trace") || +- !strcmp(name_dev, "uevent")) +- continue; +- +- if (dm_snprintf(path, sizeof(path), "%sblock/%s/%s/dev", +- _sysfs_dir, name, name_dev) == -1) { +- log_warn("Couldn't create path for %s/%s.", name, name_dev); +- continue; +- } +- +- if (_sysfs_get_dev_major_minor(path, major, minor)) { +- r = dm_strncpy(buf, name_dev, buf_size); +- break; /* found */ +- } ++ if (_sysfs_get_dev_major_minor(path, major, minor)) { ++ r = dm_strncpy(buf, name_dev, buf_size); ++ break; /* found */ + } ++ } + +- if (closedir(d_dev)) +- log_sys_debug("closedir", name); ++ if (closedir(d_dev)) ++ log_sys_debug("closedir", name); + } +- } + + if (closedir(d)) + log_sys_debug("closedir", path); +-- +2.54.0 + diff --git a/0064-cov-dev-mpath-fix-TOCTOU-race-in-holders-directory-c.patch b/0064-cov-dev-mpath-fix-TOCTOU-race-in-holders-directory-c.patch new file mode 100644 index 0000000..457e955 --- /dev/null +++ b/0064-cov-dev-mpath-fix-TOCTOU-race-in-holders-directory-c.patch @@ -0,0 +1,56 @@ +From 8483c7d96d831c06968ef585533ba06da8c36a05 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 22:01:14 +0200 +Subject: [PATCH 064/211] cov: dev-mpath: fix TOCTOU race in holders directory + check + +Remove stat() check before opendir() to eliminate TOCTOU race. +Call opendir() directly and check errno: +- ENOENT: silently skip (normal for partitions without holders) +- ENOTDIR: warn (path exists but is not a directory) +- Other errors: log debug message + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 44b9934d92c2e1ef723d58753ee1633b528582d1) +--- + lib/device/dev-mpath.c | 20 +++++++++----------- + 1 file changed, 9 insertions(+), 11 deletions(-) + +diff --git a/lib/device/dev-mpath.c b/lib/device/dev-mpath.c +index 7e99e17d0..6e71ab080 100644 +--- a/lib/device/dev-mpath.c ++++ b/lib/device/dev-mpath.c +@@ -459,21 +459,19 @@ static int _dev_is_mpath_component_sysfs(struct cmd_context *cmd, struct device + return 0; + } + +- /* also will filter out partitions */ +- if (stat(holders_path, &info)) +- return 0; +- +- if (!S_ISDIR(info.st_mode)) { +- log_warn("Path %s is not a directory.", holders_path); +- return 0; +- } +- + /* + * If any holder is a dm mpath device, then return 1; ++ * This also filters out partitions (no holders directory). + */ +- + if (!(dr = opendir(holders_path))) { +- log_debug("Device %s has no holders dir", dev_name(dev)); ++ /* Distinguish between non-existent (partitions) and errors */ ++ if (errno == ENOENT) ++ return 0; /* Normal for partitions */ ++ if (errno == ENOTDIR) { ++ log_warn("Path %s is not a directory.", holders_path); ++ return 0; ++ } ++ log_debug("Device %s has no holders dir.", dev_name(dev)); + return 0; + } + +-- +2.54.0 + diff --git a/0065-cov-dev-cache-fix-TOCTOU-race-in-devices_file_rename.patch b/0065-cov-dev-cache-fix-TOCTOU-race-in-devices_file_rename.patch new file mode 100644 index 0000000..863da50 --- /dev/null +++ b/0065-cov-dev-cache-fix-TOCTOU-race-in-devices_file_rename.patch @@ -0,0 +1,52 @@ +From 7b9f2be292c3b6c6fd4985e5b56cd61bc44b1608 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 22:13:18 +0200 +Subject: [PATCH 065/211] cov: dev-cache: fix TOCTOU race in + devices_file_rename_unused + +Remove stat() check before rename() to eliminate TOCTOU race. +Instead, call rename() directly and silently ignore ENOENT +(file doesn't exist). Other errors still call stack. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 142aa7b6ceec37b72b6d39b3c1356909b4cf4bff) +--- + lib/device/dev-cache.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +diff --git a/lib/device/dev-cache.c b/lib/device/dev-cache.c +index 15cbc1d84..24a4d91c5 100644 +--- a/lib/device/dev-cache.c ++++ b/lib/device/dev-cache.c +@@ -2019,7 +2019,6 @@ static void devices_file_rename_unused(struct cmd_context *cmd) + const char *filename; + time_t t; + struct tm *tm; +- struct stat st; + + filename = find_config_tree_str(cmd, devices_devicesfile_CFG, NULL); + +@@ -2029,9 +2028,6 @@ static void devices_file_rename_unused(struct cmd_context *cmd) + if (dm_snprintf(path, sizeof(path), "%s/devices/%s", cmd->system_dir, filename) < 0) + return; + +- if (stat(path, &st)) +- return; +- + t = time(NULL); + if (!(tm = localtime(&t))) + return; +@@ -2043,7 +2039,9 @@ static void devices_file_rename_unused(struct cmd_context *cmd) + return; + + if (rename(path, path2) < 0) { +- stack; ++ /* Silently ignore if file doesn't exist */ ++ if (errno != ENOENT) ++ stack; + return; + } + log_debug("Devices file moved to %s", path2); +-- +2.54.0 + diff --git a/0066-libdm-dbg_malloc-fix-buffer-overflow-in-dm_realloc_a.patch b/0066-libdm-dbg_malloc-fix-buffer-overflow-in-dm_realloc_a.patch new file mode 100644 index 0000000..fa64731 --- /dev/null +++ b/0066-libdm-dbg_malloc-fix-buffer-overflow-in-dm_realloc_a.patch @@ -0,0 +1,32 @@ +From 304e2acd7b40c9ebbebca1af474f5f7ba6b8a73e Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:01:35 +0200 +Subject: [PATCH 066/211] libdm: dbg_malloc: fix buffer overflow in + dm_realloc_aux + +memcpy used the old allocation size (mb->length) unconditionally. +When shrinking (new size < old size), this overflows the new buffer. +Copy the minimum of old and new sizes. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 767157754b694035d051093861cad5b9ac3494e7) +--- + libdm/mm/dbg_malloc.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/mm/dbg_malloc.c b/libdm/mm/dbg_malloc.c +index 96d2311d5..c6a9ae3ed 100644 +--- a/libdm/mm/dbg_malloc.c ++++ b/libdm/mm/dbg_malloc.c +@@ -212,7 +212,7 @@ void *dm_realloc_aux(void *p, unsigned int s, const char *file, int line) + r = dm_malloc_aux_debug(s, file, line); + + if (r && p) { +- memcpy(r, p, mb->length); ++ memcpy(r, p, (s < mb->length) ? s : mb->length); + dm_free_aux(p); + } + +-- +2.54.0 + diff --git a/0067-libdevmapper-event-fix-read-buffer-overflow-in-_daem.patch b/0067-libdevmapper-event-fix-read-buffer-overflow-in-_daem.patch new file mode 100644 index 0000000..c304a94 --- /dev/null +++ b/0067-libdevmapper-event-fix-read-buffer-overflow-in-_daem.patch @@ -0,0 +1,33 @@ +From 724efc360fb2f54611bbb56adfed56e0699de880 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:38:57 +0200 +Subject: [PATCH 067/211] libdevmapper-event: fix read() buffer overflow in + _daemon_read + +read() was called with full 'size' instead of remaining 'size - bytes', +so after a partial read, it could write past the end of the buffer. + +The server-side counterpart in dmeventd.c correctly uses 'size - bytes'. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 17ae34da124c96922b266ab7accdbcfdc11791b4) +--- + daemons/dmeventd/libdevmapper-event.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/daemons/dmeventd/libdevmapper-event.c b/daemons/dmeventd/libdevmapper-event.c +index 485f04605..36f119261 100644 +--- a/daemons/dmeventd/libdevmapper-event.c ++++ b/daemons/dmeventd/libdevmapper-event.c +@@ -249,7 +249,7 @@ static int _daemon_read(struct dm_event_fifos *fifos, + goto bad; + } + +- ret = read(fifos->server, buf + bytes, size); ++ ret = read(fifos->server, buf + bytes, size - bytes); + if (ret < 0) { + if ((errno == EINTR) || (errno == EAGAIN)) + continue; +-- +2.54.0 + diff --git a/0068-dmsetup-fix-dangling-pointer-in-_slurp_stdin-after-r.patch b/0068-dmsetup-fix-dangling-pointer-in-_slurp_stdin-after-r.patch new file mode 100644 index 0000000..cc2fde4 --- /dev/null +++ b/0068-dmsetup-fix-dangling-pointer-in-_slurp_stdin-after-r.patch @@ -0,0 +1,31 @@ +From b897d04990f31f299aab883d5055f9ca351dfe6a Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 13 Mar 2026 16:00:40 +0100 +Subject: [PATCH 068/211] dmsetup: fix dangling pointer in _slurp_stdin after + realloc + +After realloc() the buffer may move to a new address, but pos +was not updated, causing read() to write into freed memory. +Triggers EFAULT when stdin input exceeds ~128KB. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 130ddc48ea2a084852e78c232990ed8d58aaeca9) +--- + libdm/dm-tools/dmsetup.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libdm/dm-tools/dmsetup.c b/libdm/dm-tools/dmsetup.c +index 2d6ed0a71..eb77ebce4 100644 +--- a/libdm/dm-tools/dmsetup.c ++++ b/libdm/dm-tools/dmsetup.c +@@ -1263,6 +1263,7 @@ static char *_slurp_stdin(void) + return NULL; + } + buf = newbuf; ++ pos = buf + total; + } + } while (1); + +-- +2.54.0 + diff --git a/0069-export-fix-out-of-bounds-access-in-_sectors_to_units.patch b/0069-export-fix-out-of-bounds-access-in-_sectors_to_units.patch new file mode 100644 index 0000000..7bbfdb3 --- /dev/null +++ b/0069-export-fix-out-of-bounds-access-in-_sectors_to_units.patch @@ -0,0 +1,31 @@ +From 81b04fb6706d19fe9cc85cf7c7fbbe1e4a714b82 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 10:05:12 +0200 +Subject: [PATCH 069/211] export: fix out-of-bounds access in _sectors_to_units + +The loop could increment i to DM_ARRAY_SIZE(_units) before +exiting, causing out-of-bounds access on _units[i]. +However issue cannot be hit, since lvm2 supports only 8EiB. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 4f1a29639abaa3ee7fd095116627b65c83e369ab) +--- + lib/format_text/export.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/format_text/export.c b/lib/format_text/export.c +index ccc3167ca..5c120f145 100644 +--- a/lib/format_text/export.c ++++ b/lib/format_text/export.c +@@ -240,7 +240,7 @@ static int _sectors_to_units(uint64_t sectors, char *buffer, size_t s) + /* to convert to K */ + d /= 2.0; + +- for (i = 0; (d > 1024.0) && i < DM_ARRAY_SIZE(_units); ++i) ++ for (i = 0; (d > 1024.0) && i < DM_ARRAY_SIZE(_units) - 1; ++i) + d /= 1024.0; + + return dm_snprintf(buffer, s, "# %g %s", d, _units[i]) > 0; +-- +2.54.0 + diff --git a/0070-id-fix-out-of-bounds-read-in-_id_valid-with-corrupt-.patch b/0070-id-fix-out-of-bounds-read-in-_id_valid-with-corrupt-.patch new file mode 100644 index 0000000..0570850 --- /dev/null +++ b/0070-id-fix-out-of-bounds-read-in-_id_valid-with-corrupt-.patch @@ -0,0 +1,34 @@ +From b55144da93ab7a0dc8ba750afade39f4144f3f12 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 13:40:49 +0200 +Subject: [PATCH 070/211] id: fix out-of-bounds read in _id_valid with corrupt + metadata + +id->uuid is int8_t[], so a corrupt on-disk byte >= 0x80 becomes +negative and indexes before the _inverse_c[256] array. +Cast to unsigned char to keep the index within bounds. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 7966fa3369dcd5671deb732615dfe0fe5633477c) +--- + lib/id/id.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/lib/id/id.c b/lib/id/id.c +index b52121cd9..5c386ce0f 100644 +--- a/lib/id/id.c ++++ b/lib/id/id.c +@@ -96,7 +96,9 @@ static int _id_valid(struct id *id, int e) + _build_inverse(); + + for (i = 0; i < ID_LEN; i++) +- if (!_inverse_c[id->uuid[i]]) { ++ /* Cast to unsigned char: int8_t uuid with corrupt byte >= 0x80 ++ * would be negative and index before _inverse_c[] array. */ ++ if (!_inverse_c[(unsigned char)id->uuid[i]]) { + if (e) + log_error("UUID contains invalid character '%c'", id->uuid[i]); + return 0; +-- +2.54.0 + diff --git a/0071-clang-lvmcache-fix-use-after-free-in-lvmcache_update.patch b/0071-clang-lvmcache-fix-use-after-free-in-lvmcache_update.patch new file mode 100644 index 0000000..8a30e7e --- /dev/null +++ b/0071-clang-lvmcache-fix-use-after-free-in-lvmcache_update.patch @@ -0,0 +1,38 @@ +From b95b1d3fb1a0eb132dbef466305bd33f78186a19 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 24 Mar 2026 21:31:58 +0100 +Subject: [PATCH 071/211] clang: lvmcache: fix use-after-free in + lvmcache_update_vg_from_read + +_drop_vginfo() detaches info from vginfo->infos and then frees +vginfo if the infos list becomes empty. The next line accesses +vginfo->outdated_infos which is use-after-free when the last PV +info was just removed. + +Use _vginfo_detach_info() directly since we only need to unlink +info from vginfo->infos without the conditional vginfo cleanup. + +Found by clang scan-build. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 374ec4c76ed7ad596cf22f70fc8f447854c17e82) +--- + lib/cache/lvmcache.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/cache/lvmcache.c b/lib/cache/lvmcache.c +index 86d252e45..da9a55d09 100644 +--- a/lib/cache/lvmcache.c ++++ b/lib/cache/lvmcache.c +@@ -2318,7 +2318,7 @@ void lvmcache_update_vg_from_read(struct volume_group *vg, int *incorrect_pv_cla + if (!_outdated_warning++) + log_warn("See vgck --updatemetadata to clear outdated metadata."); + +- _drop_vginfo(info, vginfo); /* remove from vginfo->infos */ ++ _vginfo_detach_info(info); /* remove from vginfo->infos */ + dm_list_add(&vginfo->outdated_infos, &info->list); + } + +-- +2.54.0 + diff --git a/0072-clang-lvmlockd-fix-use-after-free-in-res_process.patch b/0072-clang-lvmlockd-fix-use-after-free-in-res_process.patch new file mode 100644 index 0000000..4bd9fbf --- /dev/null +++ b/0072-clang-lvmlockd-fix-use-after-free-in-res_process.patch @@ -0,0 +1,40 @@ +From fae11decdb12d9da660748bb181063dde5200d1f Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 24 Mar 2026 21:31:49 +0100 +Subject: [PATCH 072/211] clang: lvmlockd: fix use-after-free in res_process + +add_client_result() may call free_action(act) when LD_AF_NO_CLIENT +flag is set. The subsequent access of act->op to check for +LD_OP_DISABLE is then a use-after-free. Save the op value before +passing act to add_client_result(). + +Found by clang scan-build. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 3095302e97b9c48f2df4296a449e5ee76580f103) +--- + daemons/lvmlockd/lvmlockd-core.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/daemons/lvmlockd/lvmlockd-core.c b/daemons/lvmlockd/lvmlockd-core.c +index f7340ff3e..b3960feb7 100644 +--- a/daemons/lvmlockd/lvmlockd-core.c ++++ b/daemons/lvmlockd/lvmlockd-core.c +@@ -1989,12 +1989,13 @@ static void res_process(struct lockspace *ls, struct resource *r, + + list_for_each_entry_safe(act, safe, &r->actions, list) { + if (act->op == LD_OP_ENABLE || act->op == LD_OP_DISABLE) { ++ int is_disable = (act->op == LD_OP_DISABLE); + rv = res_able(ls, r, act); + act->result = rv; + list_del(&act->list); + add_client_result(act); + +- if (!rv && act->op == LD_OP_DISABLE) { ++ if (!rv && is_disable) { + log_debug("%s:%s free disabled", ls->name, r->name); + goto r_free; + } +-- +2.54.0 + diff --git a/0073-cov-device_id-fix-NULL-dereference-in-_dev_has_id.patch b/0073-cov-device_id-fix-NULL-dereference-in-_dev_has_id.patch new file mode 100644 index 0000000..9b76a9e --- /dev/null +++ b/0073-cov-device_id-fix-NULL-dereference-in-_dev_has_id.patch @@ -0,0 +1,32 @@ +From 58d413836eab837f113dfd741c999719d9e3ca16 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Wed, 1 Apr 2026 20:33:27 +0200 +Subject: [PATCH 073/211] cov: device_id: fix NULL dereference in _dev_has_id + +Add NULL check for idname parameter before strcmp() call. +The function could crash when called with NULL idname from +device_ids_check_serial() if du->idname was unexpectedly NULL. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 035d0bf5b8e044198f4233e42f3f8f9de011c297) +--- + lib/device/device_id.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/lib/device/device_id.c b/lib/device/device_id.c +index 4bb92cb09..6ec8f43ba 100644 +--- a/lib/device/device_id.c ++++ b/lib/device/device_id.c +@@ -1199,6 +1199,9 @@ static int _dev_has_id(struct device *dev, uint16_t idtype, const char *idname) + { + struct dev_id *id; + ++ if (!idname) ++ return 0; ++ + dm_list_iterate_items(id, &dev->ids) { + if (id->idtype != idtype) + continue; +-- +2.54.0 + diff --git a/0074-dmeventd-snapshot-fix-NULL-target_type-passed-to-log.patch b/0074-dmeventd-snapshot-fix-NULL-target_type-passed-to-log.patch new file mode 100644 index 0000000..3d7cc05 --- /dev/null +++ b/0074-dmeventd-snapshot-fix-NULL-target_type-passed-to-log.patch @@ -0,0 +1,31 @@ +From ec768d527e2f33f919d7482ae722cf837433cfd6 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:47:30 +0200 +Subject: [PATCH 074/211] dmeventd: snapshot: fix NULL target_type passed to + log_error %s + +When target_type is NULL, passing it to %s format is undefined behavior. +Use ??? as fallback to still report actual target_type when available. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 057434a6dc5def17379725b8b9c25453b286f250) +--- + daemons/dmeventd/plugins/snapshot/dmeventd_snapshot.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/daemons/dmeventd/plugins/snapshot/dmeventd_snapshot.c b/daemons/dmeventd/plugins/snapshot/dmeventd_snapshot.c +index e47747c1a..40d197297 100644 +--- a/daemons/dmeventd/plugins/snapshot/dmeventd_snapshot.c ++++ b/daemons/dmeventd/plugins/snapshot/dmeventd_snapshot.c +@@ -183,7 +183,7 @@ void process_event(struct dm_task *dmt, + + dm_get_next_target(dmt, next, &start, &length, &target_type, ¶ms); + if (!target_type || strcmp(target_type, "snapshot")) { +- log_error("Target %s is not snapshot.", target_type); ++ log_error("Target %s is not snapshot.", target_type ?: "???"); + return; + } + +-- +2.54.0 + diff --git a/0075-libdaemon-config-util-fix-NULL-dereference-in-error-.patch b/0075-libdaemon-config-util-fix-NULL-dereference-in-error-.patch new file mode 100644 index 0000000..9fc3f8e --- /dev/null +++ b/0075-libdaemon-config-util-fix-NULL-dereference-in-error-.patch @@ -0,0 +1,31 @@ +From 58160a59cafb671695d4c1149b504640d7953ed9 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:23:53 +0200 +Subject: [PATCH 075/211] libdaemon: config-util: fix NULL dereference in error + message + +When fmt (from strchr) is NULL, the error message was printing +fmt instead of next, passing NULL to printf %s. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 7458d4755e724ec49dcf9f0e36566f1e80d49f48) +--- + libdaemon/client/config-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdaemon/client/config-util.c b/libdaemon/client/config-util.c +index b0813f176..ceefc06a4 100644 +--- a/libdaemon/client/config-util.c ++++ b/libdaemon/client/config-util.c +@@ -246,7 +246,7 @@ struct dm_config_node *config_make_nodes_v(struct dm_config_tree *cft, + fmt = strchr(next, '='); + + if (!fmt) { +- log_error(INTERNAL_ERROR "Bad format string '%s'", fmt); ++ log_error(INTERNAL_ERROR "Bad format string '%s'.", next); + return NULL; + } + +-- +2.54.0 + diff --git a/0076-clang-metadata-add-NULL-check-for-pva-in-_alloc_para.patch b/0076-clang-metadata-add-NULL-check-for-pva-in-_alloc_para.patch new file mode 100644 index 0000000..e9724ab --- /dev/null +++ b/0076-clang-metadata-add-NULL-check-for-pva-in-_alloc_para.patch @@ -0,0 +1,37 @@ +From 18a1e4c8f486eacde276de49a75241578865c9e8 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 24 Mar 2026 21:38:48 +0100 +Subject: [PATCH 076/211] clang: metadata: add NULL check for pva in + _alloc_parallel_area + +The allocator's gap-filling logic can leave alloc_state areas +with NULL pva in certain edge cases. Add a defensive NULL check +before dereferencing pva->map->pv to avoid a potential NULL +pointer dereference. + +Found by clang scan-build. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 52319437d50614f782640eb43a29d56d5af98eda) +--- + lib/metadata/lv_manip.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c +index 7296f5e66..201836d73 100644 +--- a/lib/metadata/lv_manip.c ++++ b/lib/metadata/lv_manip.c +@@ -2151,6 +2151,10 @@ static int _alloc_parallel_area(struct alloc_handle *ah, uint32_t max_to_allocat + } + + pva = alloc_state->areas[s + ix_log_skip].pva; ++ if (!pva) { ++ log_error("Missing PV area for parallel allocation."); ++ return 0; ++ } + if (ah->alloc_and_split_meta && !ah->split_metadata_is_allocated) { + /* + * The metadata area goes at the front of the allocated +-- +2.54.0 + diff --git a/0077-clang-libdm-fix-dangling-parent-pointer-to-stack-var.patch b/0077-clang-libdm-fix-dangling-parent-pointer-to-stack-var.patch new file mode 100644 index 0000000..ae8cadc --- /dev/null +++ b/0077-clang-libdm-fix-dangling-parent-pointer-to-stack-var.patch @@ -0,0 +1,49 @@ +From f066ceec5967f7ac1000b707cee68adc4c7abb44 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 24 Mar 2026 21:32:21 +0100 +Subject: [PATCH 077/211] clang: libdm: fix dangling parent pointer to stack + variable in config flatten + +_override_path() uses a stack-local dummy node as the root anchor +for _find_or_make_node(). When _make_node() creates a new top-level +node, it sets node->parent = &dummy. After _override_path() returns, +the dummy goes out of scope leaving a dangling parent pointer in +the config tree returned by dm_config_flatten(). + +Clear the parent pointer for any top-level nodes that still +reference the stack variable. + +Found by clang scan-build. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 07572f306384bbc5604c0035f6297e7cd99e7b31) +--- + libdm/libdm-config.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/libdm/libdm-config.c b/libdm/libdm-config.c +index 0e8baea73..25cfa268c 100644 +--- a/libdm/libdm-config.c ++++ b/libdm/libdm-config.c +@@ -1510,13 +1510,17 @@ struct dm_pool *dm_config_memory(struct dm_config_tree *cft) + static int _override_path(const char *path, struct dm_config_node *node, void *baton) + { + struct dm_config_tree *cft = baton; +- struct dm_config_node dummy, *target; ++ struct dm_config_node dummy, *target, *cn; + dummy.child = cft->root; + if (!(target = _find_or_make_node(cft->mem, &dummy, path, 0))) + return_0; + if (!(target->v = _clone_config_value(cft->mem, node->v))) + return_0; + cft->root = dummy.child; ++ /* Clear dangling parent pointers to stack variable */ ++ for (cn = cft->root; cn; cn = cn->sib) ++ if (cn->parent == &dummy) ++ cn->parent = NULL; + return 1; + } + +-- +2.54.0 + diff --git a/0078-parse_vpd-fix-VPD-descriptor-bounds-checking.patch b/0078-parse_vpd-fix-VPD-descriptor-bounds-checking.patch new file mode 100644 index 0000000..a7cfbb1 --- /dev/null +++ b/0078-parse_vpd-fix-VPD-descriptor-bounds-checking.patch @@ -0,0 +1,73 @@ +From ce1d80012d4237ea181747b78fe125164a889f3d Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 2 Apr 2026 00:03:37 +0200 +Subject: [PATCH 078/211] parse_vpd: fix VPD descriptor bounds checking + +parse_vpd_ids had broken bounds checks in cases 0x1 (T10) and 0x8 +(SCSI name string) that compared cur_id_size against the output buffer +size (id_len = 1024) instead of the VPD input buffer. Since cur_id_size +is uint8_t (max 255 per VPD spec byte 3), those checks were always +false (dead code), and the overflow assignments were flagged by Coverity. + +The real risk is reading past vpd_datalen with malformed VPD data. + +Fix by: +- Tightening loop condition to d + 4 <= vpd_end so the 4-byte + descriptor header is always readable +- Clamping cur_id_size to available VPD bytes (vpd_end - d - 4) + instead of output buffer size +- Removing unused id_len variable + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 37ec8951e8881e906aa0b9dcd688bb71fe0d178a) +--- + lib/device/parse_vpd.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/lib/device/parse_vpd.c b/lib/device/parse_vpd.c +index 16a653a14..b67e36763 100644 +--- a/lib/device/parse_vpd.c ++++ b/lib/device/parse_vpd.c +@@ -153,14 +153,14 @@ int parse_vpd_ids(const unsigned char *vpd_data, int vpd_datalen, struct dm_list + char id[ID_BUFSIZE]; + unsigned char tmp_str[ID_BUFSIZE]; + const unsigned char *d, *cur_id_str; +- size_t id_len = ID_BUFSIZE; ++ const unsigned char *vpd_end = vpd_data + vpd_datalen; + int id_size = -1; + int type; + uint8_t cur_id_size = 0; + + memset(id, 0, ID_BUFSIZE); + for (d = vpd_data + 4; +- d < vpd_data + vpd_datalen; ++ d + 4 <= vpd_end; + d += d[3] + 4) { + memset(tmp_str, 0, sizeof(tmp_str)); + +@@ -168,8 +168,8 @@ int parse_vpd_ids(const unsigned char *vpd_data, int vpd_datalen, struct dm_list + case 0x1: + /* T10 Vendor ID */ + cur_id_size = d[3]; +- if ((size_t)(cur_id_size + 4) > id_len) +- cur_id_size = id_len - 4; ++ if (d + 4 + cur_id_size > vpd_end) ++ cur_id_size = vpd_end - d - 4; + cur_id_str = d + 4; + format_t10_id(cur_id_str, cur_id_size, tmp_str, sizeof(tmp_str)); + id_size = snprintf(id, ID_BUFSIZE, "t10.%s", tmp_str); +@@ -230,9 +230,9 @@ int parse_vpd_ids(const unsigned char *vpd_data, int vpd_datalen, struct dm_list + case 0x8: + /* SCSI name string */ + cur_id_size = d[3]; ++ if (d + 4 + cur_id_size > vpd_end) ++ cur_id_size = vpd_end - d - 4; + cur_id_str = d + 4; +- if (cur_id_size >= id_len) +- cur_id_size = id_len - 1; + memcpy(id, cur_id_str, cur_id_size); + id_size = cur_id_size; + +-- +2.54.0 + diff --git a/0079-cov-dev-ext-validate-src-bounds-in-dev_ext_enable.patch b/0079-cov-dev-ext-validate-src-bounds-in-dev_ext_enable.patch new file mode 100644 index 0000000..b327f24 --- /dev/null +++ b/0079-cov-dev-ext-validate-src-bounds-in-dev_ext_enable.patch @@ -0,0 +1,35 @@ +From 4341180f04be061e991fa40f215f000069c54243 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Wed, 1 Apr 2026 22:33:49 +0200 +Subject: [PATCH 079/211] cov: dev-ext: validate src bounds in dev_ext_enable + +Check that src is within _ext_registry array bounds before +using it as index. Prevents out of bounds access if src has +an unexpected value. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 9cbc486bdc838612c7e3381bb8c198bede81ca77) +--- + lib/device/dev-ext.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/lib/device/dev-ext.c b/lib/device/dev-ext.c +index 8f0f519b5..8521b6e0b 100644 +--- a/lib/device/dev-ext.c ++++ b/lib/device/dev-ext.c +@@ -140,6 +140,12 @@ int dev_ext_release(struct device *dev) + + int dev_ext_enable(struct device *dev, dev_ext_t src) + { ++ if (src >= DEV_EXT_NUM) { ++ log_error(INTERNAL_ERROR "%s: Invalid external source [%d].", ++ dev_name(dev), src); ++ return 0; ++ } ++ + if (dev->ext.enabled && (dev->ext.src != src) && !dev_ext_release(dev)) { + log_error("%s: Failed to enable external handle [%s].", + dev_name(dev), _ext_registry[src].name); +-- +2.54.0 + diff --git a/0080-cov-config-check-for-size-overflow-in-config_file_re.patch b/0080-cov-config-check-for-size-overflow-in-config_file_re.patch new file mode 100644 index 0000000..4b76682 --- /dev/null +++ b/0080-cov-config-check-for-size-overflow-in-config_file_re.patch @@ -0,0 +1,35 @@ +From 5a9623ddea7094bb7fd3dc1124a495be9762bf67 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Wed, 1 Apr 2026 20:56:58 +0200 +Subject: [PATCH 080/211] cov: config: check for size overflow in + config_file_read_fd + +Add overflow check for size + size2 before buffer allocation. +On 32-bit systems, the size_t addition could wrap, allocating +a small buffer followed by a large read into it. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit f9dfc2223ccf94e3e9589d8249f48a658a214339) +--- + lib/config/config.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/lib/config/config.c b/lib/config/config.c +index 53d6fc9a0..9cc010e46 100644 +--- a/lib/config/config.c ++++ b/lib/config/config.c +@@ -513,6 +513,11 @@ int config_file_read_fd(struct dm_config_tree *cft, struct device *dev, dev_io_r + + /* Ensure there is extra '\0' after end of buffer since we pass + * buffer to functions like strtoll() */ ++ if (size + size2 < size) { ++ log_error("Metadata buffer size overflow."); ++ return 0; ++ } ++ + if (!(buf = zalloc(size + size2 + 1))) { + log_error("Failed to allocate circular buffer."); + return 0; +-- +2.54.0 + diff --git a/0081-cov-format_text-validate-rlocn-bounds-before-uint32_.patch b/0081-cov-format_text-validate-rlocn-bounds-before-uint32_.patch new file mode 100644 index 0000000..c6752ae --- /dev/null +++ b/0081-cov-format_text-validate-rlocn-bounds-before-uint32_.patch @@ -0,0 +1,62 @@ +From 9eb047aa1742031f5999a8a1d22720dcd16dd432 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Wed, 1 Apr 2026 20:33:32 +0200 +Subject: [PATCH 081/211] cov: format_text: validate rlocn bounds before + uint32_t cast + +Validate rlocn offset and size fields against mda bounds before +casting to uint32_t. The on-disk rlocn fields are uint64_t but +wrap and size parameters are uint32_t, so crafted metadata with +values exceeding UINT32_MAX would silently truncate, leading to +incorrect buffer sizes. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit c42c285e1b7c062e9db736e8b8ad3646b477661a) +--- + lib/format_text/format-text.c | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/lib/format_text/format-text.c b/lib/format_text/format-text.c +index 615b92df9..3165312d7 100644 +--- a/lib/format_text/format-text.c ++++ b/lib/format_text/format-text.c +@@ -416,6 +416,17 @@ static struct volume_group *_vg_read_raw_area(struct cmd_context *cmd, + goto out; + } + ++ /* Validate rlocn fields fit within mda bounds before uint32_t cast */ ++ if (rlocn->offset >= mdah->size || ++ rlocn->size > mdah->size - MDA_HEADER_SIZE) { ++ log_error("Metadata location out of bounds (offset %llu size %llu mda %llu) on %s.", ++ (unsigned long long)rlocn->offset, ++ (unsigned long long)rlocn->size, ++ (unsigned long long)mdah->size, ++ dev_name(area->dev)); ++ goto out; ++ } ++ + if (rlocn->offset + rlocn->size > mdah->size) + wrap = (uint32_t) ((rlocn->offset + rlocn->size) - mdah->size); + +@@ -1501,6 +1512,18 @@ int read_metadata_location_summary(const struct format_type *fmt, + * at the beginning. The end of this wrapped metadata is located at an + * offset of wrap+MDA_HEADER_SIZE from area.start. + */ ++ ++ /* Validate rlocn fields fit within mda bounds before uint32_t cast */ ++ if (rlocn->offset >= mdah->size || ++ rlocn->size > mdah->size - MDA_HEADER_SIZE) { ++ log_warn("WARNING: Metadata location out of bounds (offset %llu size %llu mda %llu) on %s.", ++ (unsigned long long)rlocn->offset, ++ (unsigned long long)rlocn->size, ++ (unsigned long long)mdah->size, ++ dev_name(dev_area->dev)); ++ return 0; ++ } ++ + if (rlocn->offset + rlocn->size > mdah->size) + wrap = (uint32_t) ((rlocn->offset + rlocn->size) - mdah->size); + +-- +2.54.0 + diff --git a/0082-label-clear-DEV_IN_BCACHE-on-bcache_set_fd-failure.patch b/0082-label-clear-DEV_IN_BCACHE-on-bcache_set_fd-failure.patch new file mode 100644 index 0000000..7b33795 --- /dev/null +++ b/0082-label-clear-DEV_IN_BCACHE-on-bcache_set_fd-failure.patch @@ -0,0 +1,30 @@ +From aea719b00789152a2213f4791ac698836b2bd439 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 13:14:40 +0200 +Subject: [PATCH 082/211] label: clear DEV_IN_BCACHE on bcache_set_fd failure + +When bcache_set_fd() fails, DEV_IN_BCACHE flag was left set while +bcache_fd was reset to -1. This causes _in_bcache() to return true, +making label_scan_open() skip the actual open. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit b69f9cc41a1f5b806cf1fc088876fa89cc14c0a2) +--- + lib/label/label.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lib/label/label.c b/lib/label/label.c +index 91243494b..12a825c62 100644 +--- a/lib/label/label.c ++++ b/lib/label/label.c +@@ -563,6 +563,7 @@ static int _scan_dev_open(struct device *dev) + if (close(fd)) + log_sys_debug("close", name); + dev->bcache_fd = -1; ++ dev->flags &= ~DEV_IN_BCACHE; + return 0; + } + +-- +2.54.0 + diff --git a/0083-lv_manip-fix-copy-paste-error-in-thin-pool-metadata-.patch b/0083-lv_manip-fix-copy-paste-error-in-thin-pool-metadata-.patch new file mode 100644 index 0000000..2134879 --- /dev/null +++ b/0083-lv_manip-fix-copy-paste-error-in-thin-pool-metadata-.patch @@ -0,0 +1,31 @@ +From c78d380024e6972dac5ea79aa2989cd2276c5c5a Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 21:28:49 +0200 +Subject: [PATCH 083/211] lv_manip: fix copy-paste error in thin pool metadata + extend + +Assigning poolmetadata_sign to lp_meta.size instead of lp_meta.sign, +overwriting previously set size value. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit eea1ea49fb41352cf4f1fe59784dc08bc1669024) +--- + lib/metadata/lv_manip.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c +index 201836d73..61816bd8c 100644 +--- a/lib/metadata/lv_manip.c ++++ b/lib/metadata/lv_manip.c +@@ -6803,7 +6803,7 @@ int lv_resize(struct cmd_context *cmd, struct logical_volume *lv, + _setup_params_for_extend_metadata(lv_meta, &lp_meta); + if (lp->poolmetadata_size) { + lp_meta.size = lp->poolmetadata_size; +- lp_meta.size = lp->poolmetadata_sign; ++ lp_meta.sign = lp->poolmetadata_sign; + lp->poolmetadata_size = 0; + lp->poolmetadata_sign = SIGN_NONE; + } +-- +2.54.0 + diff --git a/0084-activate-fix-cachevol-cmeta-cdata-device-offsets.patch b/0084-activate-fix-cachevol-cmeta-cdata-device-offsets.patch new file mode 100644 index 0000000..9539e15 --- /dev/null +++ b/0084-activate-fix-cachevol-cmeta-cdata-device-offsets.patch @@ -0,0 +1,36 @@ +From d84d1cb3056afa25792e332fa813012760958c62 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Wed, 4 Feb 2026 15:32:27 +0100 +Subject: [PATCH 084/211] activate: fix cachevol cmeta/cdata device offsets + +Use actual metadata_start and data_start offsets when creating the +cmeta and cdata linear devices for cachevol, instead of assuming +metadata always starts at 0 and data at metadata_len. + +This allows for flexible cachevol layouts as specified in metadata. + +Co-Authored-By: Claude Sonnet 4.5 +(cherry picked from commit cd2f25ee0deb40161a17c7e5f427e237a0bda7bb) +--- + lib/activate/dev_manager.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/lib/activate/dev_manager.c b/lib/activate/dev_manager.c +index 8caa9f997..db3b24595 100644 +--- a/lib/activate/dev_manager.c ++++ b/lib/activate/dev_manager.c +@@ -3459,9 +3459,9 @@ static int _add_new_cvol_subdev_to_dtree(struct dev_manager *dm, + if (!(dlid_pool = build_dm_uuid(dm->mem, pool_lv, NULL))) + return_0; + +- /* add seg_area to prev load_seg: offset 0 maps to cachevol lv offset 0 */ ++ /* add seg_area to prev load_seg: map to correct offset in cachevol */ + if (!dm_tree_node_add_target_area(dnode, NULL, dlid_pool, +- meta_or_data ? 0 : lvseg->metadata_len)) ++ meta_or_data ? lvseg->metadata_start : lvseg->data_start)) + return_0; + } + +-- +2.54.0 + diff --git a/0085-merge-fix-wrong-variable-in-mirror-size-check-log_er.patch b/0085-merge-fix-wrong-variable-in-mirror-size-check-log_er.patch new file mode 100644 index 0000000..bdf5773 --- /dev/null +++ b/0085-merge-fix-wrong-variable-in-mirror-size-check-log_er.patch @@ -0,0 +1,36 @@ +From 50740fb435f1cab88363bf3dc38a331a68dc8934 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 08:39:38 +0200 +Subject: [PATCH 085/211] merge: fix wrong variable in mirror size check + log_error + +The error message was printing area index 's' as the segment number. +Use seg_count for the segment number and add 's' for the image index, +matching the format used by the adjacent mirror pointer check message. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit fcae4db4074d6d9c7d059759a2a04596ba06801a) +--- + lib/metadata/merge.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/lib/metadata/merge.c b/lib/metadata/merge.c +index b2829f9d0..2cb9b4c08 100644 +--- a/lib/metadata/merge.c ++++ b/lib/metadata/merge.c +@@ -639,9 +639,10 @@ int check_lv_segments_complete_vg(struct logical_volume *lv) + if (seg_is_mirrored(seg) && !seg_is_raid(seg) && + seg_type(seg, s) == AREA_LV && seg_lv(seg, s) && + seg_lv(seg, s)->le_count != seg->area_len) { +- log_error("LV %s: mirrored LV segment %u has " ++ log_error("LV %s: segment %u mirrored image %u has " + "wrong size %u (should be %u).", +- lv->name, s, seg_lv(seg, s)->le_count, ++ lv->name, seg_count, s, ++ seg_lv(seg, s)->le_count, + seg->area_len); + inc_error_count; + } +-- +2.54.0 + diff --git a/0086-dev_manager-fix-wrong-sizeof-in-raid-status-allocati.patch b/0086-dev_manager-fix-wrong-sizeof-in-raid-status-allocati.patch new file mode 100644 index 0000000..0fda6f8 --- /dev/null +++ b/0086-dev_manager-fix-wrong-sizeof-in-raid-status-allocati.patch @@ -0,0 +1,31 @@ +From 7b4579ac06e2debcd889666723f39bd0de800766 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 10:24:46 +0200 +Subject: [PATCH 086/211] dev_manager: fix wrong sizeof in raid status + allocation + +Copy-paste error: sizeof(struct lv_status_cache) was used instead +of sizeof(struct lv_status_raid) in dev_manager_raid_status. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 591d822bf496133a68bdd2ee98fdb4093199144a) +--- + lib/activate/dev_manager.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/activate/dev_manager.c b/lib/activate/dev_manager.c +index db3b24595..46c626de5 100644 +--- a/lib/activate/dev_manager.c ++++ b/lib/activate/dev_manager.c +@@ -1664,7 +1664,7 @@ int dev_manager_raid_status(struct dev_manager *dm, + struct dm_status_raid *sr; + + *exists = -1; +- if (!(*status = dm_pool_zalloc(dm->mem, sizeof(struct lv_status_cache)))) ++ if (!(*status = dm_pool_zalloc(dm->mem, sizeof(struct lv_status_raid)))) + return_0; + + if (!(dlid = build_dm_uuid(dm->mem, lv, layer))) +-- +2.54.0 + diff --git a/0087-cov-thin-fix-wrong-variable-in-chunk_size-error-mess.patch b/0087-cov-thin-fix-wrong-variable-in-chunk_size-error-mess.patch new file mode 100644 index 0000000..2a51e44 --- /dev/null +++ b/0087-cov-thin-fix-wrong-variable-in-chunk_size-error-mess.patch @@ -0,0 +1,30 @@ +From 3c3f8eac835e42ee6802e7e66bc059b91995ae50 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 14:34:04 +0200 +Subject: [PATCH 087/211] cov: thin: fix wrong variable in chunk_size error + message + +The error validates seg->chunk_size but printed seg->device_id. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 32d6cb664a38fb66fa361e385fc09812d9459b16) +--- + lib/thin/thin.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/thin/thin.c b/lib/thin/thin.c +index c9b40f635..85a90db38 100644 +--- a/lib/thin/thin.c ++++ b/lib/thin/thin.c +@@ -123,7 +123,7 @@ static int _thin_pool_text_import(struct lv_segment *seg, + if ((seg->chunk_size < DM_THIN_MIN_DATA_BLOCK_SIZE) || + (seg->chunk_size > DM_THIN_MAX_DATA_BLOCK_SIZE)) + return SEG_LOG_ERROR("Unsupported value %u for chunk_size", +- seg->device_id); ++ seg->chunk_size); + + if (dm_config_has_node(sn, "zero_new_blocks") && + !dm_config_get_uint32(sn, "zero_new_blocks", &zero)) +-- +2.54.0 + diff --git a/0088-vdo_manip-fix-missing-si.mem_unit-in-total-memory-ca.patch b/0088-vdo_manip-fix-missing-si.mem_unit-in-total-memory-ca.patch new file mode 100644 index 0000000..4308736 --- /dev/null +++ b/0088-vdo_manip-fix-missing-si.mem_unit-in-total-memory-ca.patch @@ -0,0 +1,33 @@ +From cc43f53099abafce472746deba9015a9f9db4373 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 08:40:26 +0200 +Subject: [PATCH 088/211] vdo_manip: fix missing si.mem_unit in total memory + calculation + +_get_sysinfo_memory() was computing total_mb without multiplying by +si.mem_unit, unlike the available_mb calculation on the line above. +When si.mem_unit != 1 (e.g. on 32-bit systems) this gave a wrong +total memory value. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit ff30ca50aca84903c802a44db8e39aa13993aeaf) +--- + lib/metadata/vdo_manip.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/metadata/vdo_manip.c b/lib/metadata/vdo_manip.c +index 21084468f..f5d04918b 100644 +--- a/lib/metadata/vdo_manip.c ++++ b/lib/metadata/vdo_manip.c +@@ -651,7 +651,7 @@ static int _get_sysinfo_memory(uint64_t *total_mb, uint64_t *available_mb) + (unsigned long long)si.freehigh >> 20, si.mem_unit); + + *available_mb = ((uint64_t)(si.freeram + si.bufferram) * si.mem_unit) >> 30; +- *total_mb = si.totalram >> 30; ++ *total_mb = ((uint64_t) si.totalram * si.mem_unit) >> 30; + + return 1; + } +-- +2.54.0 + diff --git a/0089-dmeventd-vdo-fix-name-overwrite-and-missing-max_fail.patch b/0089-dmeventd-vdo-fix-name-overwrite-and-missing-max_fail.patch new file mode 100644 index 0000000..49223c2 --- /dev/null +++ b/0089-dmeventd-vdo-fix-name-overwrite-and-missing-max_fail.patch @@ -0,0 +1,45 @@ +From 64029cb552f1b65bad1b2385bdbbfd7a63d6082c Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:45:03 +0200 +Subject: [PATCH 089/211] dmeventd: vdo: fix name overwrite and missing + max_fails init + +Fix two bugs in VDO plugin register_device(): + +1. 'state->name = "volume"' was unconditionally overwritten by + 'state->name = name' (where name="pool"). Fix by setting the + local 'name' variable instead of state->name directly. + +2. Missing 'state->max_fails = 1' initialization. Since state is + zero-allocated, max_fails starts at 0 and the exponential backoff + logic (which uses left-shift) never advances: 0 << 1 = 0. + This causes failing policy commands to retry on every single event + instead of backing off. Match the thin plugin which correctly + initializes max_fails to 1. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 1d9edbfd6d3a1ad5da0a93765350407717436461) +--- + daemons/dmeventd/plugins/vdo/dmeventd_vdo.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/daemons/dmeventd/plugins/vdo/dmeventd_vdo.c b/daemons/dmeventd/plugins/vdo/dmeventd_vdo.c +index 093e71908..536870a16 100644 +--- a/daemons/dmeventd/plugins/vdo/dmeventd_vdo.c ++++ b/daemons/dmeventd/plugins/vdo/dmeventd_vdo.c +@@ -353,10 +353,11 @@ int register_device(const char *device, + state->argv[1] = str + 1; /* 1 argument - vg/lv */ + _init_thread_signals(state); + } else if (cmd[0] == 0) { +- state->name = "volume"; /* What to use with 'others?' */ ++ name = "volume"; /* What to use with 'others?' */ + } else/* Unsupported command format */ + goto inval; + ++ state->max_fails = 1; + state->pid = -1; + state->name = name; + *user = state; +-- +2.54.0 + diff --git a/0090-lvmlockd-dlm-fix-wrong-variable-in-fopen-and-error-l.patch b/0090-lvmlockd-dlm-fix-wrong-variable-in-fopen-and-error-l.patch new file mode 100644 index 0000000..f65c38a --- /dev/null +++ b/0090-lvmlockd-dlm-fix-wrong-variable-in-fopen-and-error-l.patch @@ -0,0 +1,44 @@ +From 4eb6ecf8f54b60f12c181a590e0eb32c19a4cd9b Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:54:24 +0200 +Subject: [PATCH 090/211] lvmlockd-dlm: fix wrong variable in fopen and error + log + +1. get_local_nodeid() constructed per-entry path in 'path' but opened + 'ls_comms_path' (the parent directory) instead, making local node ID + detection non-functional. + +2. read_cluster_name() logged fd (file descriptor) instead of rv + (the read error) on read failure. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 4aa202dbe28fd2df9dc235a651cd7001b2904afb) +--- + daemons/lvmlockd/lvmlockd-dlm.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/daemons/lvmlockd/lvmlockd-dlm.c b/daemons/lvmlockd/lvmlockd-dlm.c +index 7529ad327..ec3a3b3c5 100644 +--- a/daemons/lvmlockd/lvmlockd-dlm.c ++++ b/daemons/lvmlockd/lvmlockd-dlm.c +@@ -113,7 +113,7 @@ static int read_cluster_name(char *clustername) + + rv = read(fd, clustername, MAX_ARGS); + if (rv < 0) { +- log_error("read_cluster_name: cluster name read error %d, check dlm_controld", fd); ++ log_error("read_cluster_name: cluster name read error %d, check dlm_controld", rv); + goto out; + } + clustername[rv] = 0; +@@ -247,7 +247,7 @@ static int get_local_nodeid(void) + snprintf(path, sizeof(path), "%s/%s/local", + DLM_COMMS_PATH, de->d_name); + +- if (!(file = fopen(ls_comms_path, "r"))) ++ if (!(file = fopen(path, "r"))) + continue; + str1 = fgets(line, sizeof(line), file); + if (fclose(file)) +-- +2.54.0 + diff --git a/0091-lvmlockd-sanlock-fix-wrong-variable-in-error-log.patch b/0091-lvmlockd-sanlock-fix-wrong-variable-in-error-log.patch new file mode 100644 index 0000000..0dd76a9 --- /dev/null +++ b/0091-lvmlockd-sanlock-fix-wrong-variable-in-error-log.patch @@ -0,0 +1,30 @@ +From 22fd8e58f06adfdd212ec3c0d40ade2c274588a5 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:55:45 +0200 +Subject: [PATCH 091/211] lvmlockd-sanlock: fix wrong variable in error log + +Error message printed rv (error code) twice instead of the offset value. +The success path two lines above correctly uses offset. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 975b7886a17004fb5dcf0baf1c35848d1a57cbc8) +--- + daemons/lvmlockd/lvmlockd-sanlock.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/daemons/lvmlockd/lvmlockd-sanlock.c b/daemons/lvmlockd/lvmlockd-sanlock.c +index f06af6c60..2a5720b07 100644 +--- a/daemons/lvmlockd/lvmlockd-sanlock.c ++++ b/daemons/lvmlockd/lvmlockd-sanlock.c +@@ -1004,7 +1004,7 @@ int lm_init_lv_sanlock(struct lockspace *ls, char *ls_name, char *vg_name, char + lock_args_version, (unsigned long long)offset); + } else { + log_error("S %s init_lv_san write error %d offset %llu", +- ls_name, rv, (unsigned long long)rv); ++ ls_name, rv, (unsigned long long)offset); + } + break; + } +-- +2.54.0 + diff --git a/0092-daemon-client-fix-swapped-parameters-in-log_debug.patch b/0092-daemon-client-fix-swapped-parameters-in-log_debug.patch new file mode 100644 index 0000000..2bee529 --- /dev/null +++ b/0092-daemon-client-fix-swapped-parameters-in-log_debug.patch @@ -0,0 +1,34 @@ +From d324d870e7409d2c3a84b26b37c13b60dd331869 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 20:32:03 +0200 +Subject: [PATCH 092/211] daemon-client: fix swapped parameters in log_debug + +Line 31-32: Fixed swapped parameters in debug message. The format +string suggests "%s: Opening daemon socket to %s" where the first %s +is the daemon name and the second %s is the socket path. The arguments +were swapped (i.socket, i.path) when they should be (i.path, i.socket) +according to the daemon_info struct definition where path is the daemon +binary and socket is the socket path. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit a47d90285a1172f37a713ab0969d4c8b87c314e6) +--- + libdaemon/client/daemon-client.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdaemon/client/daemon-client.c b/libdaemon/client/daemon-client.c +index 9ee88fa69..14edee63e 100644 +--- a/libdaemon/client/daemon-client.c ++++ b/libdaemon/client/daemon-client.c +@@ -28,7 +28,7 @@ daemon_handle daemon_open(daemon_info i) + struct sockaddr_un sockaddr = { .sun_family = AF_UNIX }; + + log_debug("%s: Opening daemon socket to %s for protocol %s version %d.", +- i.socket, i.path, i.protocol, i.protocol_version); ++ i.path, i.socket, i.protocol, i.protocol_version); + + if ((h.socket_fd = socket(PF_UNIX, SOCK_STREAM /* | SOCK_NONBLOCK */, 0)) < 0) { + h.error = errno; +-- +2.54.0 + diff --git a/0093-raid_manip-fix-swapped-parameters-in-error-message.patch b/0093-raid_manip-fix-swapped-parameters-in-error-message.patch new file mode 100644 index 0000000..7b29cd7 --- /dev/null +++ b/0093-raid_manip-fix-swapped-parameters-in-error-message.patch @@ -0,0 +1,47 @@ +From 8e1bfa318078918b98dd16db253518c620df73e9 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 19:14:42 +0200 +Subject: [PATCH 093/211] raid_manip: fix swapped parameters in error message + +Fix swapped parameters in error message after match_count decrement. + +The execution flow is: +1. Try to allocate match_count devices -> FAILS +2. Decrement match_count-- +3. Log error message +4. Retry with reduced count + +Before the decrement, match_count had the value that just failed. +After the decrement, match_count has the new reduced value we'll retry. + +The message should say: + "Failed to replace X (the old value). Attempting Y (the new value)." + +Current code incorrectly prints: + "Failed to replace 5 devices. Attempting to replace 6 instead." + +Fixed code correctly prints: + "Failed to replace 6 devices. Attempting to replace 5 instead." + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit cde1ea1ff15a5e1f80f55229f8b4b23f209157ea) +--- + lib/metadata/raid_manip.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/metadata/raid_manip.c b/lib/metadata/raid_manip.c +index 2a78a0bec..99c71e967 100644 +--- a/lib/metadata/raid_manip.c ++++ b/lib/metadata/raid_manip.c +@@ -6995,7 +6995,7 @@ try_again: + if (match_count > 0) { + log_error("Failed to replace %u devices." + " Attempting to replace %u instead.", +- match_count, match_count+1); ++ match_count+1, match_count); + /* + * Since we are replacing some but not all of the bad + * devices, we must set partial_activation +-- +2.54.0 + diff --git a/0094-cov-memlock-fix-open-return-value-check.patch b/0094-cov-memlock-fix-open-return-value-check.patch new file mode 100644 index 0000000..9da5a18 --- /dev/null +++ b/0094-cov-memlock-fix-open-return-value-check.patch @@ -0,0 +1,30 @@ +From 1ada320659c8c1879cecae817853b643e31b071a Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 14:24:49 +0200 +Subject: [PATCH 094/211] cov: memlock: fix open() return value check + +open() returns -1 on error and 0 is a valid file descriptor. +Using !() would incorrectly treat fd 0 as failure. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 4c64e5b70ab9803f60ce2110c7f05367f98c3de9) +--- + lib/mm/memlock.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/mm/memlock.c b/lib/mm/memlock.c +index 8b45f10ee..0e43bf624 100644 +--- a/lib/mm/memlock.c ++++ b/lib/mm/memlock.c +@@ -551,7 +551,7 @@ static void _lock_mem(struct cmd_context *cmd) + return; + } + +- if (!(_maps_fd = open(_procselfmaps, O_RDONLY))) { ++ if ((_maps_fd = open(_procselfmaps, O_RDONLY)) < 0) { + log_sys_debug("open", _procselfmaps); + return; + } +-- +2.54.0 + diff --git a/0095-filesystem-fix-nofs-early-return-checking-wrong-stru.patch b/0095-filesystem-fix-nofs-early-return-checking-wrong-stru.patch new file mode 100644 index 0000000..692b55b --- /dev/null +++ b/0095-filesystem-fix-nofs-early-return-checking-wrong-stru.patch @@ -0,0 +1,39 @@ +From 80326bcb7fa8638fd14ce8900a30e59b9c9dc2f4 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 13:32:46 +0200 +Subject: [PATCH 095/211] filesystem: fix nofs early-return checking wrong + struct + +fs_get_info() checked fsi->nofs (caller's zeroed output parameter) +instead of info.nofs (the local struct populated by fs_get_blkid). +The early-return for no-filesystem case never triggered, causing +unnecessary crypto_LUKS, mount, and resize checks. + +Result was still correct because *fsi = info copy at line 342 +propagated nofs to the caller, but the early exit was dead code. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 3606b9749042fb02117bebb0ba84f4e95ebeef57) +--- + lib/device/filesystem.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/lib/device/filesystem.c b/lib/device/filesystem.c +index adb8f0174..87f97130d 100644 +--- a/lib/device/filesystem.c ++++ b/lib/device/filesystem.c +@@ -284,8 +284,10 @@ int fs_get_info(struct cmd_context *cmd, struct logical_volume *lv, struct fs_in + return 0; + } + +- if (fsi->nofs) ++ if (info.nofs) { ++ fsi->nofs = 1; + return 1; ++ } + + /* + * If there's a LUKS dm-crypt layer over the LV, then +-- +2.54.0 + diff --git a/0096-lvconvert-fix-inverted-proc_dir-check-and-wrong-size.patch b/0096-lvconvert-fix-inverted-proc_dir-check-and-wrong-size.patch new file mode 100644 index 0000000..e90ccd2 --- /dev/null +++ b/0096-lvconvert-fix-inverted-proc_dir-check-and-wrong-size.patch @@ -0,0 +1,43 @@ +From cb232e39db78f5a11a8aefd6502ffd394c90eed9 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 11:08:44 +0200 +Subject: [PATCH 096/211] lvconvert: fix inverted proc_dir check and wrong + sizeof + +Fix two bugs: +- The proc_dir emptiness check was inverted, skipping /proc/meminfo + reading when proc_dir was available instead of when it was empty. +- memset used sizeof(cvname) instead of sizeof(format) to clear the + format buffer. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 54ec1fefa043522e4d04152998b3d66a059defe2) +--- + tools/lvconvert.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index 270bae7d0..7a17028e5 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -4538,7 +4538,7 @@ static int _lv_create_cachevol(struct cmd_context *cmd, + } + + if (find_lv(vg, cvname)) { +- memset(format, 0, sizeof(cvname)); ++ memset(format, 0, sizeof(format)); + memset(cvname, 0, sizeof(cvname)); + if (dm_snprintf(format, sizeof(format), "%s_cache%%d", lv->name) < 0) { + log_error("Failed to generate cachevol LV format."); +@@ -6236,7 +6236,7 @@ static int _check_writecache_memory(struct cmd_context *cmd, struct logical_volu + unsigned long long proc_mem_kb = 0; + char proc_meminfo[PATH_MAX]; + +- if (*cmd->proc_dir) ++ if (!*cmd->proc_dir) + goto skip_proc; + + if (dm_snprintf(proc_meminfo, sizeof(proc_meminfo), +-- +2.54.0 + diff --git a/0097-lvmlockd-idm-fix-stale-rv-check-and-missing-in-conve.patch b/0097-lvmlockd-idm-fix-stale-rv-check-and-missing-in-conve.patch new file mode 100644 index 0000000..51d65ea --- /dev/null +++ b/0097-lvmlockd-idm-fix-stale-rv-check-and-missing-in-conve.patch @@ -0,0 +1,44 @@ +From 437fa65fa0438d92c44e9ee647d1cee6cfdf416d Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:55:14 +0200 +Subject: [PATCH 097/211] lvmlockd-idm: fix stale rv check and missing & in + convert + +1. lm_lock_idm() checked stale 'rv' instead of 'rdi->op.mode' after + to_idm_mode(), so invalid lock modes were never caught. + +2. lm_convert_idm() passed vb_timestamp value as pointer (missing &), + causing ilm_write_lvb() to read from an arbitrary address. + The correct pattern with & is used in lm_unlock_idm(). + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 0a04cb3d1f1cf377c1f49453d90381ca25857957) +--- + daemons/lvmlockd/lvmlockd-idm.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/daemons/lvmlockd/lvmlockd-idm.c b/daemons/lvmlockd/lvmlockd-idm.c +index bfba4f59b..92c7e56ab 100644 +--- a/daemons/lvmlockd/lvmlockd-idm.c ++++ b/daemons/lvmlockd/lvmlockd-idm.c +@@ -547,7 +547,7 @@ int lm_lock_idm(struct lockspace *ls, struct resource *r, int ld_mode, + } + + rdi->op.mode = to_idm_mode(ld_mode); +- if (rv < 0) { ++ if (rdi->op.mode < 0) { + log_error("lock_idm invalid mode %d", ld_mode); + return -EINVAL; + } +@@ -725,7 +725,7 @@ int lm_convert_idm(struct lockspace *ls, struct resource *r, + + if (rdi->vb && r_version && (r->mode == LD_LK_EX)) { + rv = ilm_write_lvb(lmi->sock, &rdi->id, +- (char *)rdi->vb_timestamp, sizeof(uint64_t)); ++ (char *)&rdi->vb_timestamp, sizeof(uint64_t)); + if (rv < 0) { + log_error("S %s R %s convert_idm write lvb error %d", + ls->name, r->name, rv); +-- +2.54.0 + diff --git a/0098-cov-lv-fix-off-by-one-in-_sublvs_remove_after_reshap.patch b/0098-cov-lv-fix-off-by-one-in-_sublvs_remove_after_reshap.patch new file mode 100644 index 0000000..010a1a5 --- /dev/null +++ b/0098-cov-lv-fix-off-by-one-in-_sublvs_remove_after_reshap.patch @@ -0,0 +1,32 @@ +From 6bbf2f55027d9a4db50572c514fb6a37cd988431 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 14:37:02 +0200 +Subject: [PATCH 098/211] cov: lv: fix off-by-one in + _sublvs_remove_after_reshape + +The backward loop 'for (s = area_count-1; s; s--)' never checked +area index 0. While area 0 is unlikely to be marked for removal +after reshape, use a standard forward loop for clarity. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit ba2fef12d6351347e0bd2ab6cef950d47617a727) +--- + lib/metadata/lv.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c +index 2ecc4cf4e..c7b213067 100644 +--- a/lib/metadata/lv.c ++++ b/lib/metadata/lv.c +@@ -1296,7 +1296,7 @@ static int _sublvs_remove_after_reshape(const struct logical_volume *lv) + uint32_t s; + struct lv_segment *seg = first_seg(lv); + +- for (s = seg->area_count -1; s; s--) ++ for (s = 0; s < seg->area_count; s++) + if (seg_lv(seg, s)->status & LV_REMOVE_AFTER_RESHAPE) + return 1; + +-- +2.54.0 + diff --git a/0099-hints-fix-off-by-one-in-orphan-vgname-check.patch b/0099-hints-fix-off-by-one-in-orphan-vgname-check.patch new file mode 100644 index 0000000..c374a3f --- /dev/null +++ b/0099-hints-fix-off-by-one-in-orphan-vgname-check.patch @@ -0,0 +1,38 @@ +From 7fd9d29a454797512bbd18b6d560813d34a1df25 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 13:17:30 +0200 +Subject: [PATCH 099/211] hints: fix off-by-one in orphan vgname check + +Hint file lines have format: scan:/dev/sda pvid:xxx devn:8:0 vg:NAME +For orphan PVs (no VG) it writes "vg:-". + +The "vg:" prefix is 3 characters, so vgname[3] is the first char of +the actual VG name. The orphan check used vgname[4] instead of +vgname[3], so for "vg:-" it checked '\0' != '-' which is true, +letting the orphan name "-" through into hint.vgname. + +Currently masked by a downstream check (hint->vgname[0] == '-') +that independently filters orphan hints. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 3201a6fa88595b2abbec00049ce6826cda7175b5) +--- + lib/label/hints.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/label/hints.c b/lib/label/hints.c +index de36da504..43103b440 100644 +--- a/lib/label/hints.c ++++ b/lib/label/hints.c +@@ -846,7 +846,7 @@ static int _read_hint_file(struct cmd_context *cmd, struct dm_list *hints, int * + if (devn && sscanf(devn, "devn:%d:%d", &major, &minor) == 2) + hint.devt = makedev(major, minor); + +- if (vgname && (strlen(vgname) > 3) && (vgname[4] != '-')) ++ if (vgname && (strlen(vgname) > 3) && (vgname[3] != '-')) + if (!_dm_strncpy(hint.vgname, vgname + 3, sizeof(hint.vgname))) + continue; + +-- +2.54.0 + diff --git a/0100-lvmpolld-fix-comma-typo-and-off-by-one-in-timeout-ch.patch b/0100-lvmpolld-fix-comma-typo-and-off-by-one-in-timeout-ch.patch new file mode 100644 index 0000000..b281692 --- /dev/null +++ b/0100-lvmpolld-fix-comma-typo-and-off-by-one-in-timeout-ch.patch @@ -0,0 +1,47 @@ +From d74e3b3f8b3b57cb1c12642fc4a317dd0e075a19 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:27:33 +0200 +Subject: [PATCH 100/211] lvmpolld: fix comma typo and off-by-one in timeout + check + +- lvmpolld-data-utils.c: comma instead of semicolon after + _get_lvid assignment (worked by accident as comma expression) +- lvmpolld-core.c: use > UINT_MAX instead of >= UINT_MAX + in timeout argument parser + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 2869bd746d57929b099d67c1b17cbcaabc3da5f8) +--- + daemons/lvmpolld/lvmpolld-core.c | 2 +- + daemons/lvmpolld/lvmpolld-data-utils.c | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/daemons/lvmpolld/lvmpolld-core.c b/daemons/lvmpolld/lvmpolld-core.c +index 7ab0db8b4..ff47e1fbc 100644 +--- a/daemons/lvmpolld/lvmpolld-core.c ++++ b/daemons/lvmpolld/lvmpolld-core.c +@@ -771,7 +771,7 @@ static int process_timeout_arg(const char *str, unsigned *max_timeouts) + + errno = 0; + l = strtoul(str, &endptr, 10); +- if (errno || *endptr || l >= UINT_MAX) ++ if (errno || *endptr || l > UINT_MAX) + return 0; + + *max_timeouts = (unsigned) l; +diff --git a/daemons/lvmpolld/lvmpolld-data-utils.c b/daemons/lvmpolld/lvmpolld-data-utils.c +index 2520becbe..8a4c6f8b0 100644 +--- a/daemons/lvmpolld/lvmpolld-data-utils.c ++++ b/daemons/lvmpolld/lvmpolld-data-utils.c +@@ -124,7 +124,7 @@ struct lvmpolld_lv *pdlv_create(struct lvmpolld_state *ls, const char *id, + if (!pdlv || !tmp.lvmpolld_id || !tmp.lvname || !tmp.lvm_system_dir_env || !tmp.sinterval) + goto err; + +- tmp.lvid = _get_lvid(tmp.lvmpolld_id, sysdir), ++ tmp.lvid = _get_lvid(tmp.lvmpolld_id, sysdir); + + *pdlv = tmp; + +-- +2.54.0 + diff --git a/0101-pvmove_poll-fix-off-by-one-in-mirror-image-bounds-ch.patch b/0101-pvmove_poll-fix-off-by-one-in-mirror-image-bounds-ch.patch new file mode 100644 index 0000000..f0d8d57 --- /dev/null +++ b/0101-pvmove_poll-fix-off-by-one-in-mirror-image-bounds-ch.patch @@ -0,0 +1,33 @@ +From 8c0234432df5df88076b487a249ccc8c067171c1 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sat, 21 Mar 2026 23:46:44 +0100 +Subject: [PATCH 101/211] pvmove_poll: fix off-by-one in mirror image bounds + check + +_is_pvmove_image_removable uses > instead of >= to validate +mimage_to_remove against area_count. Since area indices are +0-based, index == area_count is out of bounds and would cause +seg_lv to access past the areas array. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 9dd36a05c11f9fec8f5896a77b626a4a19fac9d0) +--- + tools/pvmove_poll.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/pvmove_poll.c b/tools/pvmove_poll.c +index 58687ebdc..65e242ae9 100644 +--- a/tools/pvmove_poll.c ++++ b/tools/pvmove_poll.c +@@ -35,7 +35,7 @@ static int _is_pvmove_image_removable(struct logical_volume *mimage_lv, + return 0; + } + +- if (mimage_to_remove > mirror_seg->area_count) { ++ if (mimage_to_remove >= mirror_seg->area_count) { + log_error(INTERNAL_ERROR "Mirror image %" PRIu32 " not found in segment", + mimage_to_remove); + return 0; +-- +2.54.0 + diff --git a/0102-pvck-fix-copy-paste-error-checking-mda2_size_set.patch b/0102-pvck-fix-copy-paste-error-checking-mda2_size_set.patch new file mode 100644 index 0000000..8493379 --- /dev/null +++ b/0102-pvck-fix-copy-paste-error-checking-mda2_size_set.patch @@ -0,0 +1,31 @@ +From 142ce4f7bb4e0d2d1f62aa24fdfb27c7d0153ecf Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 11:06:50 +0200 +Subject: [PATCH 102/211] pvck: fix copy-paste error checking mda2_size_set + +The condition was checking set->mda_size_set instead of +set->mda2_size_set in the mda2 settings block, making +the mda2 size setting ignored. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit b3e913dcecfe9fe2d745546464efb1a88123a217) +--- + tools/pvck.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/pvck.c b/tools/pvck.c +index 8c7bbc92f..3be469562 100644 +--- a/tools/pvck.c ++++ b/tools/pvck.c +@@ -1631,7 +1631,7 @@ static int _dump_search(struct cmd_context *cmd, const char *dump, struct settin + mda_offset = set->mda2_offset; + set_vals++; + } +- if (set->mda_size_set) { ++ if (set->mda2_size_set) { + mda_size = set->mda2_size; + set_vals++; + } +-- +2.54.0 + diff --git a/0103-online-fix-buffer-pointer-in-write-loop.patch b/0103-online-fix-buffer-pointer-in-write-loop.patch new file mode 100644 index 0000000..d6b22e2 --- /dev/null +++ b/0103-online-fix-buffer-pointer-in-write-loop.patch @@ -0,0 +1,49 @@ +From 8a05f2db20f70a91822dbbdc79169f39d6fc368c Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 13:27:24 +0200 +Subject: [PATCH 103/211] online: fix buffer pointer in write loop + +The buffer is small enough that partial writes are unlikely, but +use a separate char pointer to correctly advance the write position +since buf is a stack array and cannot use pointer arithmetic. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit bb429948514e9cfd18791afbb934fa04020634c2) +--- + lib/device/online.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/lib/device/online.c b/lib/device/online.c +index 302b3d2d7..b5dfa29cb 100644 +--- a/lib/device/online.c ++++ b/lib/device/online.c +@@ -247,6 +247,7 @@ int online_pvid_file_create(struct cmd_context *cmd, struct device *dev, const c + { + char path[PATH_MAX]; + char buf[MAX_PVID_FILE_SIZE] = { 0 }; ++ char *bufp; + char file_vgname[NAME_LEN]; + char file_devname[NAME_LEN]; + char devname[NAME_LEN]; +@@ -302,8 +303,9 @@ int online_pvid_file_create(struct cmd_context *cmd, struct device *dev, const c + return 0; + } + ++ bufp = buf; + while (len > 0) { +- rv = write(fd, buf, len); ++ rv = write(fd, bufp, len); + if (rv < 0) { + /* file exists so it still works in part */ + log_warn("Cannot write online file for %s to %s error %d", +@@ -312,6 +314,7 @@ int online_pvid_file_create(struct cmd_context *cmd, struct device *dev, const c + log_sys_debug("close", path); + return 1; + } ++ bufp += rv; + len -= rv; + } + +-- +2.54.0 + diff --git a/0104-libdm-fix-cut-and-past-bug.patch b/0104-libdm-fix-cut-and-past-bug.patch new file mode 100644 index 0000000..732aa61 --- /dev/null +++ b/0104-libdm-fix-cut-and-past-bug.patch @@ -0,0 +1,28 @@ +From e68693f02780259d8d15fa2aeb07c70260263068 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sat, 21 Mar 2026 18:42:01 +0100 +Subject: [PATCH 104/211] libdm: fix cut and past bug + +set_read_ahead is using BLKRASET ioctl. + +(cherry picked from commit 2bb10cf33ee95f06fa4f10555fec433458c8b83f) +--- + libdm/libdm-common.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/libdm-common.c b/libdm/libdm-common.c +index 21bdd826e..3ee5a1070 100644 +--- a/libdm/libdm-common.c ++++ b/libdm/libdm-common.c +@@ -1334,7 +1334,7 @@ static int _set_read_ahead(const char *dev_name, uint32_t major, uint32_t minor, + } + + if (!*dev_name) { +- log_error("Empty device name passed to BLKRAGET"); ++ log_error("Empty device name passed to BLKRASET."); + return 0; + } + +-- +2.54.0 + diff --git a/0105-lvmpolld-fix-STDOUT-STDERR-copy-paste-error-in-poll-.patch b/0105-lvmpolld-fix-STDOUT-STDERR-copy-paste-error-in-poll-.patch new file mode 100644 index 0000000..d756f19 --- /dev/null +++ b/0105-lvmpolld-fix-STDOUT-STDERR-copy-paste-error-in-poll-.patch @@ -0,0 +1,31 @@ +From fc9e8b150de592d14cc6e9777e099a7d2f71b5c8 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 14:05:59 +0200 +Subject: [PATCH 105/211] lvmpolld: fix STDOUT/STDERR copy-paste error in poll + warning + +The fds[1] block handles STDERR but the error message said "STDOUT", +copy-pasted from the fds[0] block above. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 56a6ff90b8701564170b79153be1870fc3f90670) +--- + daemons/lvmpolld/lvmpolld-core.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/daemons/lvmpolld/lvmpolld-core.c b/daemons/lvmpolld/lvmpolld-core.c +index ff47e1fbc..65ac18ab5 100644 +--- a/daemons/lvmpolld/lvmpolld-core.c ++++ b/daemons/lvmpolld/lvmpolld-core.c +@@ -294,7 +294,7 @@ static int poll_for_output(struct lvmpolld_lv *pdlv, struct lvmpolld_thread_data + if (fds[1].revents & POLLHUP) + DEBUGLOG(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "caught err POLLHUP"); + else +- WARN(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "poll for command's STDOUT failed"); ++ WARN(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "poll for command's STDERR failed"); + + fds[1].fd = -1; + fds_count--; +-- +2.54.0 + diff --git a/0106-lvmcache-use-label_destroy-instead-of-free-for-label.patch b/0106-lvmcache-use-label_destroy-instead-of-free-for-label.patch new file mode 100644 index 0000000..2075daa --- /dev/null +++ b/0106-lvmcache-use-label_destroy-instead-of-free-for-label.patch @@ -0,0 +1,31 @@ +From cad7f79a1ec5523d36cc55332105e28d69903cd4 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 10:29:51 +0200 +Subject: [PATCH 106/211] lvmcache: use label_destroy instead of free for label + cleanup + +Use label_destroy to properly call the labeller's destroy_label +callback, matching the pattern in _lvmcache_destroy_info. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 50a6700e44bc018cb925818e2d57eeec07937f23) +--- + lib/cache/lvmcache.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/cache/lvmcache.c b/lib/cache/lvmcache.c +index da9a55d09..1effc254c 100644 +--- a/lib/cache/lvmcache.c ++++ b/lib/cache/lvmcache.c +@@ -2596,7 +2596,7 @@ update_vginfo: + if (created) { + dm_hash_remove(_pvid_hash, pvid); + info->dev->pvid[0] = 0; +- free(info->label); ++ label_destroy(info->label); + free(info); + } + return NULL; +-- +2.54.0 + diff --git a/0107-lvm-null-correct-field-after-freeing-log_rh.patch b/0107-lvm-null-correct-field-after-freeing-log_rh.patch new file mode 100644 index 0000000..7ede910 --- /dev/null +++ b/0107-lvm-null-correct-field-after-freeing-log_rh.patch @@ -0,0 +1,30 @@ +From 9ed020b671833bc27368b5de220aaf992991dcb4 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 11:29:14 +0200 +Subject: [PATCH 107/211] lvm: null correct field after freeing log_rh + +After dm_report_free(log_rh), the code was nulling report_group +(already handled above) instead of log_rh. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 9e13c72a8d8273195b27fe9c54dc643e51a5a383) +--- + tools/lvm.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/lvm.c b/tools/lvm.c +index b0bbdd8f2..47c03170a 100644 +--- a/tools/lvm.c ++++ b/tools/lvm.c +@@ -363,7 +363,7 @@ report_log: + + if (cmd->cmd_report.log_rh) { + dm_report_free(cmd->cmd_report.log_rh); +- cmd->cmd_report.report_group = NULL; ++ cmd->cmd_report.log_rh = NULL; + } + + return 0; +-- +2.54.0 + diff --git a/0108-dmeventd-fix-msg.data-memory-leak-in-_restart_dmeven.patch b/0108-dmeventd-fix-msg.data-memory-leak-in-_restart_dmeven.patch new file mode 100644 index 0000000..9b19f2e --- /dev/null +++ b/0108-dmeventd-fix-msg.data-memory-leak-in-_restart_dmeven.patch @@ -0,0 +1,33 @@ +From 71cf345a9ece2419c8e9625f9bb99d41a11d3c50 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:43:38 +0200 +Subject: [PATCH 108/211] dmeventd: fix msg.data memory leak in + _restart_dmeventd + +msg.data from GET_STATUS was not freed before the second daemon_talk() +for GET_PARAMETERS, which zeroes the struct via memset, leaking the +previous allocation. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 9124508d1f692a7bd143e7b28a3ab89b4c8c270f) +--- + daemons/dmeventd/dmeventd.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/daemons/dmeventd/dmeventd.c b/daemons/dmeventd/dmeventd.c +index 22a713b9a..0c3880787 100644 +--- a/daemons/dmeventd/dmeventd.c ++++ b/daemons/dmeventd/dmeventd.c +@@ -2203,6 +2203,9 @@ static int _restart_dmeventd(struct dm_event_fifos *fifos) + message += strlen(message) + 1; + } + ++ free(msg.data); ++ msg.data = NULL; ++ + if (version >= 2) { + if (daemon_talk(fifos, &msg, DM_EVENT_CMD_GET_PARAMETERS, "-", "-", 0, 0)) { + fprintf(stderr, "Failed to acquire parameters from old dmeventd.\n"); +-- +2.54.0 + diff --git a/0109-polldaemon-check-finish_copy-result-on-abort-path.patch b/0109-polldaemon-check-finish_copy-result-on-abort-path.patch new file mode 100644 index 0000000..627e0b4 --- /dev/null +++ b/0109-polldaemon-check-finish_copy-result-on-abort-path.patch @@ -0,0 +1,32 @@ +From ddb889f750e64b8f368493a373bf5fa7e7fe88a1 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sat, 21 Mar 2026 23:33:53 +0100 +Subject: [PATCH 109/211] polldaemon: check finish_copy result on abort path + +When update_metadata fails during segment progression, finish_copy +is called for cleanup but its return value was silently ignored. +Add stack trace on failure for diagnostics. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit f8c2e6054226f8ff8bfcfd25791640490b378b1b) +--- + tools/polldaemon.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/tools/polldaemon.c b/tools/polldaemon.c +index 1a3c8fefc..605c90779 100644 +--- a/tools/polldaemon.c ++++ b/tools/polldaemon.c +@@ -104,7 +104,8 @@ static int _check_lv_status(struct cmd_context *cmd, + if (parms->poll_fns->update_metadata && + !parms->poll_fns->update_metadata(cmd, vg, lv, lvs_changed, 0)) { + log_error("ABORTING: Segment progression failed."); +- parms->poll_fns->finish_copy(cmd, vg, lv, lvs_changed); ++ if (!parms->poll_fns->finish_copy(cmd, vg, lv, lvs_changed)) ++ stack; + return 0; + } + *finished = 0; /* Another segment */ +-- +2.54.0 + diff --git a/0110-metadata-fix-lv_raid_healthy-to-detect-metadata-alra.patch b/0110-metadata-fix-lv_raid_healthy-to-detect-metadata-alra.patch new file mode 100644 index 0000000..fdee2db --- /dev/null +++ b/0110-metadata-fix-lv_raid_healthy-to-detect-metadata-alra.patch @@ -0,0 +1,214 @@ +From 6c03e5fd5c875aa29ad761aa1cd4aa764849b026 Mon Sep 17 00:00:00 2001 +From: Peter Rajnoha +Date: Thu, 5 Feb 2026 17:09:43 +0100 +Subject: [PATCH 110/211] metadata: fix lv_raid_healthy to detect metadata + alrady updated to match device state + +Consider a RAID LV where one of the legs goes missing. + +There is a difference between these two: + + A) transient device failure + 1) A RAID LV has underlying PV representing a leg missing. + 2) The missing leg is replaced by error target. + 3) LVM metadata are not updated yet, just transient error (vgreduce not called). + 4) The missing PV is present again. + 5) A message is reported when getting LV RAID health status (for e.g. lvs): + "WARNING: RaidLV vg/lv needs to be refreshed!" + 6) After using lvchange --refresh vg/lv, the RAID LV is refreshed + and the error targets for previously missing leg is mapped onto + the actual PV again. + + B) non-transient device failure + 1) A RAID LV has underlying PV representing a leg missing. + 2) The missing PV is reduced (vgreduce called). + 3) LVM metadata are updated. + 4) The missing leg is present again. + 5) Inconsistent metadata is detected, the VG is made consistent first, + removes the actual leg from the old PV (vgck --updatemetadata vg). + 6) The message "WARNING: RaidLV vg/lv needs to be repaired!" is reported. + 7) After using lvconvert --repair vg/lv, the RAID LV is repaired and + the error targets for previously missing leg is mapped onto the + free PV. + +Before, lv_raid_healthy could not differentiate between A) and B). + +Now, lv_raid_healthy sets 'raid_need_t *need' output arguments where the value is one of: + + - RAID_NEED_ERROR (an error occurred while trying to acquire the healthy status) + - RAID_NEED_REFRESH (refresh is needed for transient failure) + - RAID_NEED_REPAIR (repair is needed for non-transient failure) + - RAID_NEED_REFRESH_OR_REPAIR (either refresh or repair, can't determine) + +(cherry picked from commit 6104a78a87bbdc2d4a84791ea45c3df18422a28c) +--- + lib/metadata/lv.c | 87 ++++++++++++++++++++++++++++++++++++++++------- + lib/metadata/lv.h | 9 ++++- + 2 files changed, 83 insertions(+), 13 deletions(-) + +diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c +index c7b213067..d201244a7 100644 +--- a/lib/metadata/lv.c ++++ b/lib/metadata/lv.c +@@ -1230,13 +1230,17 @@ static int lv_raid_integrity_image_in_sync(const struct logical_volume *lv_iorig + * _lv_raid_healthy + * @lv: A RAID_IMAGE, RAID_META, or RAID logical volume. + * +- * Returns: 1 if healthy, 0 if device is not health ++ * Returns: ++ * 1 if LV of RAID type healthy ++ * 0 if LV of RAID type not healthy ('need' set appropriately if not NULL) + */ +-int lv_raid_healthy(const struct logical_volume *lv) ++int lv_raid_healthy(const struct logical_volume *lv, raid_need_t *need) + { + unsigned s; + char *raid_health; ++ size_t raid_health_len; + struct lv_segment *seg, *raid_seg = NULL; ++ raid_need_t r_need = RAID_NEED_ERROR; + + /* + * If the LV is not active locally, +@@ -1247,7 +1251,7 @@ int lv_raid_healthy(const struct logical_volume *lv) + + if (!lv_is_raid_type(lv)) { + log_error(INTERNAL_ERROR "%s is not of RAID type", lv->name); +- return 0; ++ goto out_need; + } + + if (lv_is_raid(lv)) +@@ -1257,20 +1261,55 @@ int lv_raid_healthy(const struct logical_volume *lv) + + if (!raid_seg) { + log_error("Failed to find RAID segment for %s", lv->name); +- return 0; ++ goto out_need; + } + + if (!seg_is_raid(raid_seg)) { + log_error(INTERNAL_ERROR "%s on %s is not a RAID segment.", + raid_seg->lv->name, lv->name); +- return 0; ++ goto out_need; + } + +- if (!lv_raid_dev_health(raid_seg->lv, &raid_health)) +- return_0; ++ if (!lv_raid_dev_health(raid_seg->lv, &raid_health)) { ++ stack; ++ goto out_need; ++ } + +- if (lv_is_raid(lv)) +- return (strchr(raid_health, 'D')) ? 0 : 1; ++ raid_health_len = strlen(raid_health); ++ ++ if (lv_is_raid(lv)) { ++ if (raid_seg->area_count == raid_health_len) { ++ /* ++ * Counts match - positions correspond. ++ * Check each leg - only report unhealthy if a device is down ++ * but the metadata expects it to be on a real device (not error segment). ++ * If a device shows 'D' but the corresponding image LV is virtual ++ * (error segment from vgreduce --removemissing), that's expected and ++ * doesn't need a refresh. ++ */ ++ for (s = 0; s < raid_seg->area_count; s++) { ++ if (raid_health[s] == 'D') { ++ if (lv_is_virtual(seg_lv(raid_seg, s))) ++ r_need = RAID_NEED_REPAIR; ++ else ++ r_need = RAID_NEED_REFRESH; ++ goto out_need; ++ } ++ } ++ return 1; ++ } ++ ++ /* ++ * Counts don't match (reshape in progress) - can't reliably ++ * correlate health positions with metadata, fall back to ++ * behavior where any 'D' means unhealthy. ++ */ ++ if (strchr(raid_health, 'D')) { ++ r_need = RAID_NEED_REFRESH_OR_REPAIR; ++ goto out_need; ++ } ++ return 1; ++ } + + /* Find out which sub-LV this is. */ + for (s = 0; s < raid_seg->area_count; s++) +@@ -1281,13 +1320,37 @@ int lv_raid_healthy(const struct logical_volume *lv) + log_error(INTERNAL_ERROR + "sub-LV %s was not found in raid segment", + lv->name); +- return 0; ++ goto out_need; + } + +- if (raid_health[s] == 'D') +- return 0; ++ if (raid_seg->area_count == raid_health_len) { ++ /* Counts match - positions correspond */ ++ /* If sub-LV is virtual (error segment), 'D' is expected - not unhealthy */ ++ if (raid_health[s] == 'D') { ++ if (lv_is_virtual(lv)) ++ r_need = RAID_NEED_REPAIR; ++ else ++ r_need = RAID_NEED_REFRESH; ++ goto out_need; ++ } ++ return 1; ++ } + ++ /* ++ * Counts don't match (reshape in progress) - can't reliably ++ * correlate health position with sub-LV, fall back to ++ * behavior where any 'D' means unhealthy. ++ */ ++ if (strchr(raid_health, 'D')) { ++ r_need = RAID_NEED_REFRESH_OR_REPAIR; ++ goto out_need; ++ } + return 1; ++ ++out_need: ++ if (need) ++ *need = r_need; ++ return 0; + } + + /* Helper: check for any sub LVs after a disk removing reshape */ +diff --git a/lib/metadata/lv.h b/lib/metadata/lv.h +index 0596ed86b..3bf9d4211 100644 +--- a/lib/metadata/lv.h ++++ b/lib/metadata/lv.h +@@ -139,7 +139,6 @@ const struct logical_volume *lv_lock_holder(const struct logical_volume *lv); + const struct logical_volume *lv_committed(const struct logical_volume *lv); + int lv_mirror_image_in_sync(const struct logical_volume *lv); + int lv_raid_image_in_sync(const struct logical_volume *lv); +-int lv_raid_healthy(const struct logical_volume *lv); + const char *lvseg_name(const struct lv_segment *seg); + uint64_t lvseg_start(const struct lv_segment *seg); + struct dm_list *lvseg_devices(struct dm_pool *mem, const struct lv_segment *seg); +@@ -153,6 +152,14 @@ char *lvseg_seg_le_ranges_str(struct dm_pool *mem, const struct lv_segment *seg) + struct dm_list *lvseg_seg_metadata_le_ranges(struct dm_pool *mem, const struct lv_segment *seg); + char *lvseg_seg_metadata_le_ranges_str(struct dm_pool *mem, const struct lv_segment *seg); + ++typedef enum { ++ RAID_NEED_ERROR, ++ RAID_NEED_REFRESH, ++ RAID_NEED_REPAIR, ++ RAID_NEED_REFRESH_OR_REPAIR, /* can't determine */ ++} raid_need_t; ++int lv_raid_healthy(const struct logical_volume *lv, raid_need_t *need); ++ + /* LV kernel properties */ + int lv_kernel_major(const struct logical_volume *lv); + int lv_kernel_minor(const struct logical_volume *lv); +-- +2.54.0 + diff --git a/0111-lv_manip-detect-insufficient-space-for-RAID-with-spl.patch b/0111-lv_manip-detect-insufficient-space-for-RAID-with-spl.patch new file mode 100644 index 0000000..d092478 --- /dev/null +++ b/0111-lv_manip-detect-insufficient-space-for-RAID-with-spl.patch @@ -0,0 +1,61 @@ +From af3088f447aace9e6e87de388aa2da5dd6273c40 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Wed, 22 Oct 2025 16:44:09 +0200 +Subject: [PATCH 111/211] lv_manip: detect insufficient space for RAID with + split metadata + +When approx_alloc reduces allocation and we have RAID/mirror with +split metadata (alloc_and_split_meta), verify that we allocated at +least 1 extent per data area. + +If total_area_len < area_count, the integer division during parallel +area allocation (area_len = max_to_allocate / ah->area_multiple) results +in area_len=0 for individual data areas. This causes those areas to be +skipped (empty allocation), creating RAID image LVs with no segments. + +This create invalid metadata with LVs without segments. + +Example: +- Need 52 extents total (4 data areas x 13 extents) +- Metadata allocated: 4 extents (4 rmeta x 1 extent each) +- Only 1 extent remains for data +- Result: area_len = 1/4 = 0, all data areas get empty allocation + +Fix by failing allocation early with clear error message instead of +creating corrupted RAID LVs that trigger internal errors. + +Resolves: https://issues.redhat.com/browse/RHEL-116884 + +Co-Authored-By: Claude +(cherry picked from commit d2715e01f585c55f76215002db470c752ad208e3) +--- + lib/metadata/lv_manip.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c +index 61816bd8c..42e3f2489 100644 +--- a/lib/metadata/lv_manip.c ++++ b/lib/metadata/lv_manip.c +@@ -3494,6 +3494,19 @@ static int _allocate(struct alloc_handle *ah, + lv ? lv->name : ""); + goto out; + } ++ /* ++ * For RAID/mirror with split metadata, verify we allocated ++ * at least 1 extent per data area. If total_area_len is less ++ * than area_count, integer division during allocation would ++ * result in some areas getting 0 extents, creating LVs with ++ * no segments which triggers internal errors during vg_write. ++ */ ++ if (ah->alloc_and_split_meta && ah->total_area_len < ah->area_count) { ++ log_error("Insufficient suitable allocatable extents for logical volume %s: " ++ "%u extents found but at least %u required (1 extent per data image).", ++ lv ? lv->name : "", ah->total_area_len, ah->area_count); ++ goto out; ++ } + log_verbose("Found fewer %sallocatable extents " + "for logical volume %s than requested: using %" PRIu32 " extents (reduced by %u).", + can_split ? "" : "contiguous ", +-- +2.54.0 + diff --git a/0112-libdm-deptree-cancel-delay_resume-for-new-children-w.patch b/0112-libdm-deptree-cancel-delay_resume-for-new-children-w.patch new file mode 100644 index 0000000..d3d5684 --- /dev/null +++ b/0112-libdm-deptree-cancel-delay_resume-for-new-children-w.patch @@ -0,0 +1,103 @@ +From f64c7195de1d8a111b8f5a7054c5f4af2f5eb483 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Wed, 15 Oct 2025 18:07:00 +0200 +Subject: [PATCH 112/211] libdm: deptree: cancel delay_resume for new children + when parent is inactive + +When preloading children of an inactive parent node (other than root), +cancel the delay_resume_if_new flag to allow immediate resume of new devices. + +This fixes a specific issue with pvmove operations on RAID devices where: + +1. The RAID logical volume has not been instantiated yet (inactive parent) +2. A pvmove device needs to be created for one of the RAID legs +3. The RAID table requires the leg LV to be active before loading +4. Therefore, the pvmove device must be resumed immediately, not delayed + +Background: +When both RAID and pvmove devices are being created from scratch, +the pvmove device can be started immediately without leaking direct +access to the PV - all I/O goes through appropriate targets from the start. + +However, if the RAID device is already active, we can only preload the +new pvmove table. In that case, a full suspend must occur before resuming +the new table, ensuring all I/O continues through the existing table +until the resume point. + +(cherry picked from commit 48015ecb3c674b0f7326f94a44af4d93c77f0fbe) +--- + device_mapper/libdm-deptree.c | 23 ++++++++++++++++++++--- + libdm/libdm-deptree.c | 23 ++++++++++++++++++++--- + 2 files changed, 40 insertions(+), 6 deletions(-) + +diff --git a/device_mapper/libdm-deptree.c b/device_mapper/libdm-deptree.c +index 2d5829c9c..98c0fa814 100644 +--- a/device_mapper/libdm-deptree.c ++++ b/device_mapper/libdm-deptree.c +@@ -3363,9 +3363,26 @@ int dm_tree_preload_children(struct dm_tree_node *dnode, + + /* Preload children first */ + while ((child = dm_tree_next_child(&handle, dnode, 0))) { +- /* Propagate delay of resume from parent node */ +- if (dnode->props.delay_resume_if_new > 1) +- child->props.delay_resume_if_new = dnode->props.delay_resume_if_new; ++ /* When a parent node (other than root) is inactive, we cannot delay ++ * the resume of a new device. ++ * For example, preloading a RAID table with a pvmoved leg requires the ++ * leg LV to be active before loading the RAID LV, so the pvmove device must ++ * be resumed immediately. ++ * This scenario only occurs when neither the RAID nor pvmove device has ++ * been instantiated yet. In this case, starting the pvmove device does ++ * not leak access to the PV device without going through the mirror target. ++ * However, if the RAID is already active and running, we can only preload ++ * the new pvmove device, and a full suspend must occur before resuming ++ * the new table with the running pvmove. So until the resume point ++ * any IO is going through the existing table line and not via pvmove target. ++ */ ++ if ((child->props.delay_resume_if_new > 1) && ++ !dnode->info.exists && ++ (dnode != &dnode->dtree->root)) { /* Ignore when parent is root node */ ++ log_debug("%s with inactive parent cancels delay_resume_if_new.", ++ _node_name(child)); ++ child->props.delay_resume_if_new = 0; ++ } + + /* Skip existing non-device-mapper devices */ + if (!child->info.exists && child->info.major) +diff --git a/libdm/libdm-deptree.c b/libdm/libdm-deptree.c +index b0f476b3b..318b23ecc 100644 +--- a/libdm/libdm-deptree.c ++++ b/libdm/libdm-deptree.c +@@ -2906,9 +2906,26 @@ int dm_tree_preload_children(struct dm_tree_node *dnode, + + /* Preload children first */ + while ((child = dm_tree_next_child(&handle, dnode, 0))) { +- /* Propagate delay of resume from parent node */ +- if (dnode->props.delay_resume_if_new > 1) +- child->props.delay_resume_if_new = dnode->props.delay_resume_if_new; ++ /* When a parent node (other than root) is inactive, we cannot delay ++ * the resume of a new device. ++ * For example, preloading a RAID table with a pvmoved leg requires the ++ * leg LV to be active before loading the RAID LV, so the pvmove device must ++ * be resumed immediately. ++ * This scenario only occurs when neither the RAID nor pvmove device has ++ * been instantiated yet. In this case, starting the pvmove device does ++ * not leak access to the PV device without going through the mirror target. ++ * However, if the RAID is already active and running, we can only preload ++ * the new pvmove device, and a full suspend must occur before resuming ++ * the new table with the running pvmove. So until the resume point ++ * any IO is going through the existing table line and not via pvmove target. ++ */ ++ if ((child->props.delay_resume_if_new > 1) && ++ !dnode->info.exists && ++ (dnode != &dnode->dtree->root)) { /* Ignore when parent is root node */ ++ log_debug("%s with inactive parent cancels delay_resume_if_new.", ++ _node_name(child)); ++ child->props.delay_resume_if_new = 0; ++ } + + /* Skip existing non-device-mapper devices */ + if (!child->info.exists && child->info.major) +-- +2.54.0 + diff --git a/0113-integrity-fix-adding-integrity-to-RAID.patch b/0113-integrity-fix-adding-integrity-to-RAID.patch new file mode 100644 index 0000000..6587a83 --- /dev/null +++ b/0113-integrity-fix-adding-integrity-to-RAID.patch @@ -0,0 +1,139 @@ +From ec6b806b893bedda0356b825f9d775f546debbae Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Mon, 29 Sep 2025 14:03:42 +0200 +Subject: [PATCH 113/211] integrity: fix adding integrity to RAID + +Fix several issues in integrity addition to RAID logical volumes and +improve error handling and cleanup: + +Synchronization and activation fixes: +- Add sync_local_dev_names() before LV activation to handle potential + udev deactivation in-flight from metadata device wiping +- Consolidate deactivate_lv() calls to avoid code duplication in + _set_integrity_block_size() error path + +Missing segment relationship: +- Add missing add_seg_to_segs_using_this_lv() call for imeta LV to + properly establish segment relationships + +Enhanced error recovery: +- Improve error path cleanup in case integrity addition fails + +(cherry picked from commit c33233cb88961a51e0effad02510f5f01bb930d4) +--- + lib/metadata/integrity_manip.c | 59 ++++++++++++++++++++++++++-------- + 1 file changed, 45 insertions(+), 14 deletions(-) + +diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c +index be2d458b3..e03d422b2 100644 +--- a/lib/metadata/integrity_manip.c ++++ b/lib/metadata/integrity_manip.c +@@ -515,7 +515,7 @@ int lv_add_integrity_to_raid(struct logical_volume *lv, struct integrity_setting + struct logical_volume *imeta_lvs[DEFAULT_RAID_MAX_IMAGES]; + struct cmd_context *cmd = lv->vg->cmd; + struct volume_group *vg = lv->vg; +- struct logical_volume *lv_image, *lv_imeta; ++ struct logical_volume *lv_image, *lv_imeta, *lv_iorig; + struct lv_segment *seg_top, *seg_image; + struct pv_list *pvl; + const struct segment_type *segtype; +@@ -526,6 +526,7 @@ int lv_add_integrity_to_raid(struct logical_volume *lv, struct integrity_setting + int lbs_4k = 0, lbs_512 = 0, lbs_unknown = 0; + int pbs_4k = 0, pbs_512 = 0, pbs_unknown = 0; + int is_active; ++ int r; + + memset(imeta_lvs, 0, sizeof(imeta_lvs)); + +@@ -659,6 +660,8 @@ int lv_add_integrity_to_raid(struct logical_volume *lv, struct integrity_setting + } + + if (!is_active) { ++ if (!sync_local_dev_names(cmd)) ++ stack; + /* checking block size of fs on the lv requires the lv to be active */ + if (!activate_lv(cmd, lv)) { + log_error("Failed to activate LV to check block size %s", display_lvname(lv)); +@@ -673,19 +676,18 @@ int lv_add_integrity_to_raid(struct logical_volume *lv, struct integrity_setting + * integrity block size chosen based on device logical block size and + * file system block size. + */ +- if (!_set_integrity_block_size(cmd, lv, is_active, settings, lbs_4k, lbs_512, pbs_4k, pbs_512)) { +- if (!is_active && !deactivate_lv(cmd, lv)) +- stack; +- goto_bad; +- } + +- if (!is_active) { +- if (!deactivate_lv(cmd, lv)) { +- log_error("Failed to deactivate LV after checking block size %s", display_lvname(lv)); +- goto bad; +- } ++ if (!(r = _set_integrity_block_size(cmd, lv, is_active, settings, lbs_4k, lbs_512, pbs_4k, pbs_512))) ++ stack; ++ ++ if (!is_active && !deactivate_lv(cmd, lv)) { ++ log_error("Failed to deactivate LV %s after checking block size.", display_lvname(lv)); ++ goto bad; + } + ++ if (!r) ++ goto bad; ++ + /* + * For each rimage, move its segments to a new rimage_iorig and give + * the rimage a new integrity segment. +@@ -727,6 +729,8 @@ int lv_add_integrity_to_raid(struct logical_volume *lv, struct integrity_setting + seg_image->integrity_data_sectors = lv_image->size; + seg_image->integrity_meta_dev = lv_imeta; + seg_image->integrity_recalculate = 1; ++ if (!add_seg_to_segs_using_this_lv(lv_imeta, seg_image)) ++ goto_bad; + + memcpy(&seg_image->integrity_settings, settings, sizeof(struct integrity_settings)); + set = &seg_image->integrity_settings; +@@ -801,11 +805,38 @@ bad: + for (s = 0; s < DEFAULT_RAID_MAX_IMAGES; s++) { + if (!imeta_lvs[s]) + continue; +- if (!lv_remove(imeta_lvs[s])) +- log_error("New integrity metadata LV may require manual removal."); ++ ++ lv_image = seg_lv(seg_top, s); ++ seg_image = first_seg(lv_image); ++ lv_iorig = seg_lv(seg_image, 0); ++ if (lv_image->status & INTEGRITY) { ++ if (!remove_layer_from_lv(lv_image, lv_iorig) || ++ !remove_seg_from_segs_using_this_lv(seg_image->integrity_meta_dev, ++ seg_image)) { ++ log_error("Aborting. Cannot remove integrity layer from LV %s.", display_lvname(lv_image)); ++ return 0; ++ } ++ ++ lv_image->status &= ~INTEGRITY; ++ imeta_lvs[s]->status &= ~INTEGRITY_METADATA; ++ ++ lv_set_visible(imeta_lvs[s]); ++ lv_set_visible(lv_iorig); ++ ++ if (!lv_remove(lv_iorig)) { ++ log_error("Aborting. Cannot remove new integrity origin LV %s.", ++ display_lvname(lv_iorig)); ++ return 0; ++ } ++ } ++ if (!lv_remove(imeta_lvs[s])) { ++ log_error("Aborting. Cannot remove new integrity metadata LV %s.", ++ display_lvname(imeta_lvs[s])); ++ return 0; ++ } + } + } +- ++ + if (!vg_write(vg) || !vg_commit(vg)) + log_error("New integrity metadata LV may require manual removal."); + +-- +2.54.0 + diff --git a/0114-integrity-fix-activation-race-during-setup.patch b/0114-integrity-fix-activation-race-during-setup.patch new file mode 100644 index 0000000..a554086 --- /dev/null +++ b/0114-integrity-fix-activation-race-during-setup.patch @@ -0,0 +1,36 @@ +From a61fbb2bb54a29d03a00175c7e124c6ebe2e536a Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 2 Oct 2025 15:54:12 +0200 +Subject: [PATCH 114/211] integrity: fix activation race during setup + +Add synchronization after deactivate_lv() to prevent races between +deactivation and subsequent activation using the same udev cookie. + +This ensures proper ordering when wiping prepared integrity metadata +devices during RAID integrity setup, affecting both active and inactive +code paths. + +(cherry picked from commit 42e3a8705ec2911e256e14e541a2c97f5b404e15) +--- + lib/metadata/integrity_manip.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c +index e03d422b2..26b644266 100644 +--- a/lib/metadata/integrity_manip.c ++++ b/lib/metadata/integrity_manip.c +@@ -685,7 +685,10 @@ int lv_add_integrity_to_raid(struct logical_volume *lv, struct integrity_setting + goto bad; + } + +- if (!r) ++ if (!sync_local_dev_names(cmd)) ++ stack; ++ ++ if (!r) + goto bad; + + /* +-- +2.54.0 + diff --git a/0115-snapshot-reject-lvreduce-that-would-truncate-COW-exc.patch b/0115-snapshot-reject-lvreduce-that-would-truncate-COW-exc.patch new file mode 100644 index 0000000..af324d5 --- /dev/null +++ b/0115-snapshot-reject-lvreduce-that-would-truncate-COW-exc.patch @@ -0,0 +1,295 @@ +From abaf179f7ecb2945b6a5f5c795e60c80445c4f97 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 26 Feb 2026 20:24:46 +0100 +Subject: [PATCH 115/211] snapshot: reject lvreduce that would truncate COW + exception data + +Add a safety check in _lv_resize_check_type() that blocks lvreduce on +an active CoW snapshot LV when the requested new size would truncate +already-allocated exception blocks. + +The check reads the exact used_sectors / total_sectors counts from the +DM snapshot target (via new lv_snapshot_status() / dev_manager_snapshot_status()) +with a DM flush so that in-flight writes are accounted for before the +comparison. + +Fixes: https://github.com/lvmteam/lvm2/issues/164 + +Co-Authored-By: Claude Sonnet 4.6 +(cherry picked from commit 21d5277eee3822c64b26f1a98b1d84764d612bd8) +--- + lib/activate/activate.c | 34 ++++++++++++++ + lib/activate/activate.h | 3 ++ + lib/activate/dev_manager.c | 77 ++++++++++++++++++++++++++++++++ + lib/activate/dev_manager.h | 6 +++ + lib/metadata/lv_manip.c | 52 +++++++++++++++++++++ + lib/metadata/metadata-exported.h | 5 +++ + 6 files changed, 177 insertions(+) + +diff --git a/lib/activate/activate.c b/lib/activate/activate.c +index 50b8523ed..efeeaf26e 100644 +--- a/lib/activate/activate.c ++++ b/lib/activate/activate.c +@@ -258,6 +258,12 @@ int lv_snapshot_percent(const struct logical_volume *lv, dm_percent_t *percent) + { + return 0; + } ++int lv_snapshot_status(const struct logical_volume *lv, ++ int flush, ++ struct lv_status_snapshot **status) ++{ ++ return 0; ++} + int lv_mirror_percent(struct cmd_context *cmd, const struct logical_volume *lv, + int wait, dm_percent_t *percent, uint32_t *event_nr) + { +@@ -999,6 +1005,34 @@ int lv_snapshot_percent(const struct logical_volume *lv, dm_percent_t *percent) + return r; + } + ++/* ++ * Get COW snapshot status for an active COW snapshot LV. ++ * If flush is non-zero a DM flush is issued so in-flight writes are ++ * reflected in the counts before the status is read. ++ * On success returns 1 with *status populated. ++ * Caller must call dm_pool_destroy((*status)->mem) when done. ++ * Returns 0 if the LV is inactive or the query fails. ++ */ ++int lv_snapshot_status(const struct logical_volume *lv, ++ int flush, ++ struct lv_status_snapshot **status) ++{ ++ struct dev_manager *dm; ++ int exists; ++ ++ if (!(dm = dev_manager_create(lv->vg->cmd, lv->vg->name, 1))) ++ return_0; ++ ++ if (!dev_manager_snapshot_status(dm, lv, flush, status, &exists)) { ++ dev_manager_destroy(dm); ++ if (exists) ++ stack; ++ return 0; ++ } ++ ++ return 1; ++} ++ + /* FIXME Merge with snapshot_percent */ + int lv_mirror_percent(struct cmd_context *cmd, const struct logical_volume *lv, + int wait, dm_percent_t *percent, uint32_t *event_nr) +diff --git a/lib/activate/activate.h b/lib/activate/activate.h +index 155936135..34ca7adb8 100644 +--- a/lib/activate/activate.h ++++ b/lib/activate/activate.h +@@ -187,6 +187,9 @@ int lv_check_transient(struct logical_volume *lv); + * Returns 1 if percent has been set, else 0. + */ + int lv_snapshot_percent(const struct logical_volume *lv, dm_percent_t *percent); ++int lv_snapshot_status(const struct logical_volume *lv, ++ int flush, ++ struct lv_status_snapshot **status); + int lv_mirror_percent(struct cmd_context *cmd, const struct logical_volume *lv, + int wait, dm_percent_t *percent, uint32_t *event_nr); + int lv_raid_percent(const struct logical_volume *lv, dm_percent_t *percent); +diff --git a/lib/activate/dev_manager.c b/lib/activate/dev_manager.c +index 46c626de5..7a7c6a475 100644 +--- a/lib/activate/dev_manager.c ++++ b/lib/activate/dev_manager.c +@@ -1620,6 +1620,83 @@ int dev_manager_snapshot_percent(struct dev_manager *dm, + return 1; + } + ++/* ++ * Get COW snapshot status for an active COW snapshot LV. ++ * If flush is non-zero a DM flush is issued so that in-flight writes are ++ * accounted for before the status is read. ++ * On success *status is populated and (*status)->mem must be destroyed by ++ * the caller. Returns 1 on success, 0 on failure. *exists is set to 0 if ++ * the DM device does not exist (inactive LV), 1 if it does. ++ */ ++int dev_manager_snapshot_status(struct dev_manager *dm, ++ const struct logical_volume *lv, ++ int flush, ++ struct lv_status_snapshot **status, ++ int *exists) ++{ ++ int r = 0; ++ const struct logical_volume *snap_lv; ++ const char *dlid; ++ struct dm_task *dmt; ++ struct dm_info info; ++ uint64_t start, length; ++ char *type = NULL; ++ char *params = NULL; ++ ++ *exists = -1; ++ if (!(*status = dm_pool_zalloc(dm->mem, sizeof(struct lv_status_snapshot)))) ++ return_0; ++ ++ if (lv_is_merging_cow(lv)) ++ /* must check status of origin for a merging snapshot */ ++ snap_lv = origin_from_cow(lv); ++ else ++ snap_lv = lv; ++ ++ if (!(dlid = build_dm_uuid(dm->mem, snap_lv, NULL))) ++ return_0; ++ ++ if (!(dmt = _setup_task_run(DM_DEVICE_STATUS, &info, NULL, dlid, 0, 0, 0, 0, flush, 0))) ++ return_0; ++ ++ if (!(*exists = info.exists)) ++ goto out; ++ ++ log_debug_activation("Checking snapshot status for LV %s.", ++ display_lvname(snap_lv)); ++ ++ dm_get_next_target(dmt, NULL, &start, &length, &type, ¶ms); ++ ++ if (!type || strcmp(type, TARGET_NAME_SNAPSHOT) || !params) { ++ log_error("Expected snapshot segment type for %s but got %s.", ++ display_lvname(snap_lv), type ? type : "NULL"); ++ goto out; ++ } ++ ++ if (!dm_get_status_snapshot(dm->mem, params, &(*status)->snap)) ++ goto_out; ++ ++ (*status)->mem = dm->mem; ++ ++ if ((*status)->snap->invalid) ++ (*status)->usage = DM_PERCENT_INVALID; ++ else if ((*status)->snap->merge_failed) ++ (*status)->usage = LVM_PERCENT_MERGE_FAILED; ++ else if ((*status)->snap->has_metadata_sectors && ++ (*status)->snap->used_sectors == (*status)->snap->metadata_sectors) ++ (*status)->usage = DM_PERCENT_0; ++ else if ((*status)->snap->used_sectors == (*status)->snap->total_sectors) ++ (*status)->usage = DM_PERCENT_100; ++ else ++ (*status)->usage = dm_make_percent((*status)->snap->used_sectors, ++ (*status)->snap->total_sectors); ++ r = 1; ++out: ++ dm_task_destroy(dmt); ++ ++ return r; ++} ++ + /* FIXME Merge with snapshot_percent, auto-detecting target type */ + /* FIXME Cope with more than one target */ + int dev_manager_mirror_percent(struct dev_manager *dm, +diff --git a/lib/activate/dev_manager.h b/lib/activate/dev_manager.h +index 353c3c7d2..92c374a28 100644 +--- a/lib/activate/dev_manager.h ++++ b/lib/activate/dev_manager.h +@@ -20,6 +20,7 @@ + + struct logical_volume; + struct lv_activate_opts; ++struct lv_status_snapshot; + struct volume_group; + struct cmd_context; + struct dev_manager; +@@ -56,6 +57,11 @@ int dev_manager_info(struct cmd_context *cmd, const struct logical_volume *lv, + int dev_manager_snapshot_percent(struct dev_manager *dm, + const struct logical_volume *lv, + dm_percent_t *percent); ++int dev_manager_snapshot_status(struct dev_manager *dm, ++ const struct logical_volume *lv, ++ int flush, ++ struct lv_status_snapshot **status, ++ int *exists); + int dev_manager_mirror_percent(struct dev_manager *dm, + const struct logical_volume *lv, int wait, + dm_percent_t *percent, uint32_t *event_nr); +diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c +index 42e3f2489..4397f0bc2 100644 +--- a/lib/metadata/lv_manip.c ++++ b/lib/metadata/lv_manip.c +@@ -5816,6 +5816,55 @@ static int _lv_reduce_vdo_discard(struct cmd_context *cmd, + return 1; + } + ++static int _lv_resize_check_cow_reduce(struct logical_volume *lv, ++ uint64_t new_size) ++{ ++ struct cmd_context *cmd = lv->vg->cmd; ++ struct lv_status_snapshot *snap_status; ++ int r; ++ ++ /* ++ * COW snapshot must be active to check how many exception blocks are ++ * in use. Reject when inactive to avoid silently truncating data. ++ * If the snapshot content is not needed, remove it with lvremove. ++ */ ++ if (!lv_is_active(lv_lock_holder(lv))) { ++ log_error("Inactive snapshot %s cannot be reduced " ++ "(activate or remove it).", ++ display_lvname(lv)); ++ return 0; ++ } ++ ++ /* ++ * Reject reduce if it would truncate exception data already written ++ * to the COW store. Flush so that in-flight writes are reflected ++ * in the exception counts before we compare. ++ */ ++ if ((r = lv_snapshot_status(lv, 1, &snap_status))) { ++ /* merge_failed cannot occur here: merging snapshots are ++ * rejected earlier in _lv_resize_check_type(); kept as ++ * a defensive check. ++ * overflow mode is currently not supported by lvm2 ++ * kept as a defensive check. */ ++ if (snap_status->snap->invalid || ++ snap_status->snap->merge_failed || ++ snap_status->snap->overflow) { ++ log_error("Invalid snapshot %s cannot be reduced, only removed.", ++ display_lvname(lv)); ++ r = 0; ++ } else if (new_size < snap_status->snap->used_sectors) { ++ log_error("Cannot reduce snapshot %s to below %s (%.2f%% full, would lose exception data).", ++ display_lvname(lv), ++ display_size(cmd, snap_status->snap->used_sectors), ++ dm_percent_to_round_float(snap_status->usage, 2)); ++ r = 0; ++ } ++ dm_pool_destroy(snap_status->mem); ++ } ++ ++ return r; ++} ++ + static int _lv_resize_check_type(struct logical_volume *lv, + struct lvresize_params *lp) + { +@@ -5888,6 +5937,9 @@ static int _lv_resize_check_type(struct logical_volume *lv, + log_error("Cannot reduce LV with integrity."); + return 0; + } ++ if (lv_is_cow(lv) && ++ !_lv_resize_check_cow_reduce(lv, (uint64_t)lp->extents * lv->vg->extent_size)) ++ return_0; + } else if (lp->resize == LV_EXTEND) { + if (lv_is_thin_pool_metadata(lv) && + (!(seg = find_pool_seg(first_seg(lv))) || +diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h +index 7ec81f27d..a89d77284 100644 +--- a/lib/metadata/metadata-exported.h ++++ b/lib/metadata/metadata-exported.h +@@ -1201,6 +1201,11 @@ int vg_remove_snapshot(struct logical_volume *cow); + + int validate_snapshot_origin(const struct logical_volume *origin_lv); + ++struct lv_status_snapshot { ++ struct dm_pool *mem; ++ struct dm_status_snapshot *snap; ++ dm_percent_t usage; ++}; + + int vg_check_status(const struct volume_group *vg, uint64_t status); + +-- +2.54.0 + diff --git a/0116-snapshot-reject-resize-of-merging-CoW-snapshot.patch b/0116-snapshot-reject-resize-of-merging-CoW-snapshot.patch new file mode 100644 index 0000000..cb07a50 --- /dev/null +++ b/0116-snapshot-reject-resize-of-merging-CoW-snapshot.patch @@ -0,0 +1,36 @@ +From c0bd6195c00ef6d0e620c61ab05464424329a2e1 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 26 Feb 2026 21:57:52 +0100 +Subject: [PATCH 116/211] snapshot: reject resize of merging CoW snapshot + +A snapshot that is in the process of merging back into its origin +cannot be resized - the kernel runs a snapshot-merge target during +the merge and resizing the COW store while the merge is in progress +is not supported and could corrupt the operation. + +Co-Authored-By: Claude Sonnet 4.6 +(cherry picked from commit 152b0fe2ca6c5ac5482332b9352098a6f78d7d61) +--- + lib/metadata/lv_manip.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c +index 4397f0bc2..783cd2c66 100644 +--- a/lib/metadata/lv_manip.c ++++ b/lib/metadata/lv_manip.c +@@ -5870,6 +5870,12 @@ static int _lv_resize_check_type(struct logical_volume *lv, + { + struct lv_segment *seg; + ++ if (lv_is_merging_cow(lv)) { ++ log_error("Cannot resize merging snapshot %s.", ++ display_lvname(lv)); ++ return 0; ++ } ++ + if (lv_is_origin(lv)) { + if (lp->resize == LV_REDUCE) { + log_error("Snapshot origin volumes cannot be reduced in size yet."); +-- +2.54.0 + diff --git a/0117-lvresize-skip-fs-handling-for-CoW-snapshot-COW-store.patch b/0117-lvresize-skip-fs-handling-for-CoW-snapshot-COW-store.patch new file mode 100644 index 0000000..6162372 --- /dev/null +++ b/0117-lvresize-skip-fs-handling-for-CoW-snapshot-COW-store.patch @@ -0,0 +1,57 @@ +From 00b13dac560f293fa2a64b1e7e9dc0248bc88b45 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 26 Feb 2026 16:31:43 +0100 +Subject: [PATCH 117/211] lvresize: skip fs handling for CoW snapshot COW store + LVs + +The COW store LV has linear segments internally (it stores exception +blocks), so lv_is_linear() returned true for it. This caused the +fsopt disable check to be skipped, and LVM incorrectly ran fs_get_info() +on the snapshot device which presents the origin filesystem data. + +The result was a misleading error: + File system xfs found on fedora/root-snap. + File system size (9.00 GiB) is larger than the requested size (5.00 GiB). + File system reduce is required and not supported (xfs). + +Fix by explicitly excluding CoW snapshot LVs (lv_is_cow) from filesystem +handling regardless of their underlying segment type. + +Fixes: https://gitlab.com/lvmteam/lvm2/-/issues/27 +Fixes: https://github.com/lvmteam/lvm2/issues/163 + +TODO: might be worth to implemement -V options to allow resize +of snapshot volume itself. + +Co-Authored-By: Claude Sonnet 4.6 +(cherry picked from commit 99639a37fb73b4a1e044ec822a562fb4608dda0e) +--- + lib/metadata/lv_manip.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c +index 783cd2c66..0fce14f21 100644 +--- a/lib/metadata/lv_manip.c ++++ b/lib/metadata/lv_manip.c +@@ -7050,11 +7050,15 @@ int lv_resize(struct cmd_context *cmd, struct logical_volume *lv, + + /* + * Disable fsopt if LV type cannot hold a file system. ++ * CoW snapshot LVs (the COW store) do not hold a filesystem even ++ * though their segments are linear; the filesystem lives on the ++ * origin LV and the COW store only holds exception blocks. + */ + if (lp->fsopt[0] && +- !(lv_is_linear(lv) || lv_is_striped(lv) || lv_is_raid(lv) || +- lv_is_mirror(lv) || lv_is_thin_volume(lv) || lv_is_vdo(lv) || +- lv_is_cache(lv) || lv_is_writecache(lv))) { ++ (lv_is_cow(lv) || ++ !(lv_is_linear(lv) || lv_is_striped(lv) || lv_is_raid(lv) || ++ lv_is_mirror(lv) || lv_is_thin_volume(lv) || lv_is_vdo(lv) || ++ lv_is_cache(lv) || lv_is_writecache(lv)))) { + log_print_unless_silent("Ignoring fs resizing options for LV type %s.", + seg ? seg->segtype->name : "unknown"); + lp->fsopt[0] = '\0'; +-- +2.54.0 + diff --git a/0118-metadata-fix-vg_split_mdas-losing-first-_move_mdas-r.patch b/0118-metadata-fix-vg_split_mdas-losing-first-_move_mdas-r.patch new file mode 100644 index 0000000..2dc4b4e --- /dev/null +++ b/0118-metadata-fix-vg_split_mdas-losing-first-_move_mdas-r.patch @@ -0,0 +1,35 @@ +From 5c95e79a235f01c50f4d44a5a31b6afe6711958d Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 08:36:06 +0200 +Subject: [PATCH 118/211] metadata: fix vg_split_mdas losing first _move_mdas + result + +The second assignment to common_mda was overwriting the result from +the first _move_mdas() call. If a common MDA was found in the in_use +list but not in the ignored list, the information was lost. Use |= +to accumulate results from both calls. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 14ba69b358c3c25be7fc3cc461677430ebd35d09) +--- + lib/metadata/metadata.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/lib/metadata/metadata.c b/lib/metadata/metadata.c +index d15682fdb..ab037fd06 100644 +--- a/lib/metadata/metadata.c ++++ b/lib/metadata/metadata.c +@@ -1402,8 +1402,8 @@ int vg_split_mdas(struct cmd_context *cmd __attribute__((unused)), + + common_mda = _move_mdas(vg_from, vg_to, + mdas_from_in_use, mdas_to_in_use); +- common_mda = _move_mdas(vg_from, vg_to, +- mdas_from_ignored, mdas_to_ignored); ++ common_mda |= _move_mdas(vg_from, vg_to, ++ mdas_from_ignored, mdas_to_ignored); + + if ((dm_list_empty(mdas_from_in_use) && + dm_list_empty(mdas_from_ignored)) || +-- +2.54.0 + diff --git a/0119-libdm-stats-fix-bitwise-AND-and-use-escaped-aux_data.patch b/0119-libdm-stats-fix-bitwise-AND-and-use-escaped-aux_data.patch new file mode 100644 index 0000000..599cc01 --- /dev/null +++ b/0119-libdm-stats-fix-bitwise-AND-and-use-escaped-aux_data.patch @@ -0,0 +1,43 @@ +From c51e71383068fc61ebfc8a241509454cda48c3fd Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:04:54 +0200 +Subject: [PATCH 119/211] libdm: stats: fix bitwise AND and use escaped + aux_data + +Fix two bugs: +- Histogram parsing used bitwise & instead of logical && to check + for unexpected characters, causing some chars to be missed. +- stats_create message used unescaped aux_data instead of + aux_data_escaped, defeating the escaping done just above. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 55f9f953c663a3f3545c6afe73e9d2d43ce7dcf1) +--- + libdm/libdm-stats.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/libdm/libdm-stats.c b/libdm/libdm-stats.c +index 53c3c5480..6e6d63a8b 100644 +--- a/libdm/libdm-stats.c ++++ b/libdm/libdm-stats.c +@@ -1248,7 +1248,7 @@ static int _stats_parse_histogram(struct dm_pool *mem, char *hist_str, + + if (*c == ':') + c++; +- else if (*c & (*c != '\n')) ++ else if (*c && (*c != '\n')) + /* Expected ':', '\n', or NULL. */ + goto badchar; + +@@ -2060,7 +2060,7 @@ static int _stats_create_region(struct dm_stats *dms, uint64_t *region_id, + " %s %s %s", (start || len) ? range : "-", + (step < 0) ? "/" : "", + (uint64_t)llabs(step), +- opt_args, program_id, aux_data) < 0) { ++ opt_args, program_id, aux_data_escaped) < 0) { + err = "message"; + goto_bad; + } +-- +2.54.0 + diff --git a/0120-libdm-report-fix-missing-negation-in-time-range-not-.patch b/0120-libdm-report-fix-missing-negation-in-time-range-not-.patch new file mode 100644 index 0000000..2aece25 --- /dev/null +++ b/0120-libdm-report-fix-missing-negation-in-time-range-not-.patch @@ -0,0 +1,32 @@ +From 103e03c497d82b7338780b25549852c9345d0fb8 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:06:47 +0200 +Subject: [PATCH 120/211] libdm: report: fix missing negation in time range + not-equal + +The NOT-EQUAL case for time comparison was returning the same +expression as EQUAL when a range was specified, missing the +negation. The int and double comparison functions have it correct. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit f3ffd13dd1d91559812579b63779d91559467968) +--- + libdm/libdm-report.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/libdm-report.c b/libdm/libdm-report.c +index 87c588c3c..f2f0774cd 100644 +--- a/libdm/libdm-report.c ++++ b/libdm/libdm-report.c +@@ -1731,7 +1731,7 @@ static int _cmp_field_time(struct dm_report *rh, + case FLD_CMP_EQUAL: + return range ? ((val >= sel1) && (val <= sel2)) : val == sel1; + case FLD_CMP_NOT|FLD_CMP_EQUAL: +- return range ? ((val >= sel1) && (val <= sel2)) : val != sel1; ++ return range ? !((val >= sel1) && (val <= sel2)) : val != sel1; + case FLD_CMP_TIME|FLD_CMP_GT: + if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_TIME, &val, fs)) + return 0; +-- +2.54.0 + diff --git a/0121-libdm-report-fix-row-sorting-of-percent-fields.patch b/0121-libdm-report-fix-row-sorting-of-percent-fields.patch new file mode 100644 index 0000000..16da045 --- /dev/null +++ b/0121-libdm-report-fix-row-sorting-of-percent-fields.patch @@ -0,0 +1,62 @@ +From 9af697082ac096eb6eaff7acf7ebaef81a4a2940 Mon Sep 17 00:00:00 2001 +From: Peter Rajnoha +Date: Thu, 26 Mar 2026 16:34:04 +0100 +Subject: [PATCH 121/211] libdm: report: fix row sorting of percent fields +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +_row_compare was treating the sort_value of DM_REPORT_FIELD_TYPE_PERCENT +fields as plain char pointer, but it is actually an integer. This +caused incorrect sorting of these fields. + +For example, before this patch: + + ❯ lvs -o lv_name,data_percent -O data_percent vg + lv_name data_percent + pool2 20.00 + pool 100.00 + lvol3 0.00 + lvol4 0.00 + lvol7 0.00 + lvol6 1.95 + lvol2 9.77 + +With this patch applied: + + ❯ lvs -o lv_name,data_percent -O data_percent vg + lv_name data_percent + lvol3 0.00 + lvol4 0.00 + lvol7 0.00 + lvol6 1.95 + lvol2 9.77 + pool2 20.00 + pool 100.00 + +(cherry picked from commit 1e80cdad1e7e20825040e9a80ffeff39719d5ca8) +--- + libdm/libdm-report.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/libdm/libdm-report.c b/libdm/libdm-report.c +index f2f0774cd..6022fef8f 100644 +--- a/libdm/libdm-report.c ++++ b/libdm/libdm-report.c +@@ -4623,9 +4623,10 @@ static int _row_compare(const void *a, const void *b) + sfa = (*rowa->sort_fields)[cnt]; + sfb = (*rowb->sort_fields)[cnt]; + if (sfa->props->flags & +- ((DM_REPORT_FIELD_TYPE_NUMBER) | +- (DM_REPORT_FIELD_TYPE_SIZE) | +- (DM_REPORT_FIELD_TYPE_TIME))) { ++ (DM_REPORT_FIELD_TYPE_NUMBER | ++ DM_REPORT_FIELD_TYPE_SIZE | ++ DM_REPORT_FIELD_TYPE_PERCENT | ++ DM_REPORT_FIELD_TYPE_TIME)) { + const uint64_t numa = + *(const uint64_t *) sfa->sort_value; + const uint64_t numb = +-- +2.54.0 + diff --git a/0122-libdm-fix-row-sorting-for-string-list-fields.patch b/0122-libdm-fix-row-sorting-for-string-list-fields.patch new file mode 100644 index 0000000..7b80506 --- /dev/null +++ b/0122-libdm-fix-row-sorting-for-string-list-fields.patch @@ -0,0 +1,80 @@ +From f62fa4b34736978650523e2ffed0d6d0acb249a1 Mon Sep 17 00:00:00 2001 +From: Peter Rajnoha +Date: Thu, 26 Mar 2026 12:42:32 +0100 +Subject: [PATCH 122/211] libdm: fix row sorting for string list fields +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +_row_compare was treating the sort_value of DM_REPORT_FIELD_TYPE_STRING_LIST +fields as a plain char pointer, but it is actually a pointer to +struct str_list_sort_value. This caused strcmp to compare the raw bytes +of the struct (starting with a pointer) instead of the actual string +content, producing effectively random sort order for string list fields. + +Fix by extracting the display string (sortval->value) from +str_list_sort_value before comparing with strcmp. + +For example, before this patch: + + ❯ lvs -o lv_name,lv_layout -O lv_layout vg + lv_name lv_layout + lvol5 raid,raid1 + lvol4 thin,sparse + lvol3 thin,sparse + lvol2 thin,sparse + lvol0 raid,raid1 + pool thin,pool + +With this patch applied: + + ❯ lvs -o lv_name,lv_layout -O lv_layout vg + lv_name lv_layout + lvol0 raid,raid1 + lvol5 raid,raid1 + pool thin,pool + lvol2 thin,sparse + lvol3 thin,sparse + lvol4 thin,sparse + +Co-Authored-By: Claude +(cherry picked from commit 424e995f6daeb56eeccec4b3d54d151d4e259040) +--- + libdm/libdm-report.c | 20 +++++++++++++++----- + 1 file changed, 15 insertions(+), 5 deletions(-) + +diff --git a/libdm/libdm-report.c b/libdm/libdm-report.c +index 6022fef8f..13bc9a1e5 100644 +--- a/libdm/libdm-report.c ++++ b/libdm/libdm-report.c +@@ -4640,12 +4640,22 @@ static int _row_compare(const void *a, const void *b) + } else { /* FLD_DESCENDING */ + return (numa < numb) ? 1 : -1; + } ++ } else if (sfa->props->flags & DM_REPORT_FIELD_TYPE_STRING_LIST) { ++ int cmp = strcmp(((const struct str_list_sort_value *) sfa->sort_value)->value, ++ ((const struct str_list_sort_value *) sfb->sort_value)->value); ++ ++ if (!cmp) ++ continue; ++ ++ if (sfa->props->flags & FLD_ASCENDING) { ++ return (cmp > 0) ? 1 : -1; ++ } else { /* FLD_DESCENDING */ ++ return (cmp < 0) ? 1 : -1; ++ } + } else { +- /* DM_REPORT_FIELD_TYPE_STRING +- * DM_REPORT_FIELD_TYPE_STRING_LIST */ +- const char *stra = (const char *) sfa->sort_value; +- const char *strb = (const char *) sfb->sort_value; +- int cmp = strcmp(stra, strb); ++ /* DM_REPORT_FIELD_TYPE_STRING and others */ ++ int cmp = strcmp((const char *) sfa->sort_value, ++ (const char *) sfb->sort_value); + + if (!cmp) + continue; +-- +2.54.0 + diff --git a/0123-libdm-report-fix-row-sorting-for-string-list-fields-.patch b/0123-libdm-report-fix-row-sorting-for-string-list-fields-.patch new file mode 100644 index 0000000..dd87996 --- /dev/null +++ b/0123-libdm-report-fix-row-sorting-for-string-list-fields-.patch @@ -0,0 +1,130 @@ +From 78dc9788be1a9296340f66b3168a6fd48dbf31b1 Mon Sep 17 00:00:00 2001 +From: Peter Rajnoha +Date: Thu, 26 Mar 2026 12:42:32 +0100 +Subject: [PATCH 123/211] libdm: report: fix row sorting for string list fields + (continued) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +(extending commit 424e995f6daeb56eeccec4b3d54d151d4e259040) + +_row_compare was treating the sort_value of DM_REPORT_FIELD_TYPE_STRING_LIST +fields as plain char pointer, but it was actually a pointer to +struct str_list_sort_value. This caused incorrect sorting of these fields. + +Also add an internal error report if a field type is not handled +for sorting, so we notice if a new type is added without a sorting +handler. + +For example, before this patch: + + ❯ lvs -o lv_name,lv_layout -O lv_layout vg + lv_name lv_layout + lvol5 raid,raid1 + lvol4 thin,sparse + lvol3 thin,sparse + lvol2 thin,sparse + lvol0 raid,raid1 + pool thin,pool + +With this patch applied: + + ❯ lvs -o lv_name,lv_layout -O lv_layout vg + lv_name lv_layout + lvol0 raid,raid1 + lvol5 raid,raid1 + pool thin,pool + lvol2 thin,sparse + lvol3 thin,sparse + lvol4 thin,sparse + +Co-Authored-By: Claude +(cherry picked from commit fabee343f79327bcfd9ca653bfcd4af4c03ad693) +--- + libdm/libdm-report.c | 52 ++++++++++++++++++++------------------------ + 1 file changed, 24 insertions(+), 28 deletions(-) + +diff --git a/libdm/libdm-report.c b/libdm/libdm-report.c +index 13bc9a1e5..a92f96720 100644 +--- a/libdm/libdm-report.c ++++ b/libdm/libdm-report.c +@@ -4618,10 +4618,12 @@ static int _row_compare(const void *a, const void *b) + const struct row *rowb = *(const struct row * const *) b; + const struct dm_report_field *sfa, *sfb; + uint32_t cnt; ++ int cmp; + + for (cnt = 0; cnt < rowa->rh->keys_count; cnt++) { + sfa = (*rowa->sort_fields)[cnt]; + sfb = (*rowb->sort_fields)[cnt]; ++ + if (sfa->props->flags & + (DM_REPORT_FIELD_TYPE_NUMBER | + DM_REPORT_FIELD_TYPE_SIZE | +@@ -4632,40 +4634,34 @@ static int _row_compare(const void *a, const void *b) + const uint64_t numb = + *(const uint64_t *) sfb->sort_value; + +- if (numa == numb) +- continue; ++ cmp = (numa == numb) ? 0 : (numa > numb) ? 1 : -1; ++ } else if (sfa->props->flags & ++ (DM_REPORT_FIELD_TYPE_STRING | ++ DM_REPORT_FIELD_TYPE_STRING_LIST)) { ++ const char *stra, *strb; + +- if (sfa->props->flags & FLD_ASCENDING) { +- return (numa > numb) ? 1 : -1; +- } else { /* FLD_DESCENDING */ +- return (numa < numb) ? 1 : -1; ++ if (sfa->props->flags & DM_REPORT_FIELD_TYPE_STRING_LIST) { ++ stra = ((const struct str_list_sort_value *) sfa->sort_value)->value; ++ strb = ((const struct str_list_sort_value *) sfb->sort_value)->value; ++ } else { ++ stra = (const char *) sfa->sort_value; ++ strb = (const char *) sfb->sort_value; + } +- } else if (sfa->props->flags & DM_REPORT_FIELD_TYPE_STRING_LIST) { +- int cmp = strcmp(((const struct str_list_sort_value *) sfa->sort_value)->value, +- ((const struct str_list_sort_value *) sfb->sort_value)->value); + +- if (!cmp) +- continue; +- +- if (sfa->props->flags & FLD_ASCENDING) { +- return (cmp > 0) ? 1 : -1; +- } else { /* FLD_DESCENDING */ +- return (cmp < 0) ? 1 : -1; +- } ++ cmp = strcmp(stra, strb); + } else { +- /* DM_REPORT_FIELD_TYPE_STRING and others */ +- int cmp = strcmp((const char *) sfa->sort_value, +- (const char *) sfb->sort_value); ++ log_err_once(INTERNAL_ERROR "_row_compare: unhandled field type: %#x", ++ sfa->props->flags & DM_REPORT_FIELD_TYPE_MASK); ++ return 0; ++ } + +- if (!cmp) +- continue; ++ if (!cmp) ++ continue; + +- if (sfa->props->flags & FLD_ASCENDING) { +- return (cmp > 0) ? 1 : -1; +- } else { /* FLD_DESCENDING */ +- return (cmp < 0) ? 1 : -1; +- } +- } ++ if (sfa->props->flags & FLD_ASCENDING) ++ return (cmp > 0) ? 1 : -1; ++ else /* FLD_DESCENDING */ ++ return (cmp < 0) ? 1 : -1; + } + + return 0; /* Identical */ +-- +2.54.0 + diff --git a/0124-device_id-fix-checking-wrong-buffer-after-format_gen.patch b/0124-device_id-fix-checking-wrong-buffer-after-format_gen.patch new file mode 100644 index 0000000..0b8e260 --- /dev/null +++ b/0124-device_id-fix-checking-wrong-buffer-after-format_gen.patch @@ -0,0 +1,31 @@ +From e106d5239e4687f27cefff9812b3028111df6838 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 08:59:00 +0200 +Subject: [PATCH 124/211] device_id: fix checking wrong buffer after + format_general_id + +Check outbuf (the output buffer) instead of buf (the input buffer) +after format_general_id, matching the correct pattern at line 805. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 4116375f61cad895d0bce2f2ff0ee03ef5188e85) +--- + lib/device/device_id.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/device/device_id.c b/lib/device/device_id.c +index 6ec8f43ba..a55074c3c 100644 +--- a/lib/device/device_id.c ++++ b/lib/device/device_id.c +@@ -821,7 +821,7 @@ static int _dev_read_sys_serial(struct cmd_context *cmd, struct device *dev, + ret = 0; + if (ret) { + format_general_id((const char *)buf, sizeof(buf), (unsigned char *)outbuf, outbufsize); +- if (buf[0]) ++ if (outbuf[0]) + return 1; + } + } +-- +2.54.0 + diff --git a/0125-device_id-prevent-incorrect-dm-uuid-idtype.patch b/0125-device_id-prevent-incorrect-dm-uuid-idtype.patch new file mode 100644 index 0000000..ae2b579 --- /dev/null +++ b/0125-device_id-prevent-incorrect-dm-uuid-idtype.patch @@ -0,0 +1,111 @@ +From 56b263eb9f539e7bc07b0666780048addbfcaede Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Thu, 6 Nov 2025 16:45:28 -0600 +Subject: [PATCH 125/211] device_id: prevent incorrect dm uuid idtype + +If a dm-uuid-based device was added to system.devices, and +the wrong dm-based type was specified, then the system.devices +entry would have the correct idname but the wrong idtype. e.g. + +lvmdevices --adddev /dev/mapper/mpatha --deviceidtype crypt_uuid +would add an entry with: IDTYPE=crypt_uuid IDNAME=mpath- +rather than the correct: IDTYPE=mpath_uuid IDNAME=mpath- + +This mistaken type would not affect lvm, and the device would +be entirely usable. The dm-based types (mpath, crypt, lvmlv) +were interchangable because they all read the device id from +the same dm/uuid sysfs file. + +Fix this by adding a missing check that the dm uuid prefix is +correct for the specified idtype. Also, accept an existing +system.devices file which contains this mistake. + +(cherry picked from commit f0b991a9ef9b0e232cf38df44693a1b935ed93d3) +--- + lib/device/device_id.c | 54 +++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 53 insertions(+), 1 deletion(-) + +diff --git a/lib/device/device_id.c b/lib/device/device_id.c +index a55074c3c..91c998fe2 100644 +--- a/lib/device/device_id.c ++++ b/lib/device/device_id.c +@@ -845,9 +845,22 @@ char *device_id_system_read(struct cmd_context *cmd, struct device *dev, uint16_ + _dev_read_sys_serial(cmd, dev, sysbuf, sizeof(sysbuf)); + break; + case DEV_ID_TYPE_MPATH_UUID: ++ if (!dev_dm_uuid(cmd, dev, sysbuf, sizeof(sysbuf))) ++ stack; ++ if (sysbuf[0] && !dm_uuid_has_prefix(sysbuf, "mpath-")) ++ sysbuf[0] = '\0'; ++ break; + case DEV_ID_TYPE_CRYPT_UUID: ++ if (!dev_dm_uuid(cmd, dev, sysbuf, sizeof(sysbuf))) ++ stack; ++ if (sysbuf[0] && !dm_uuid_has_prefix(sysbuf, "CRYPT-")) ++ sysbuf[0] = '\0'; ++ break; + case DEV_ID_TYPE_LVMLV_UUID: +- (void)dev_dm_uuid(cmd, dev, sysbuf, sizeof(sysbuf)); ++ if (!dev_dm_uuid(cmd, dev, sysbuf, sizeof(sysbuf))) ++ stack; ++ if (sysbuf[0] && !dm_uuid_has_prefix(sysbuf, "LVM-")) ++ sysbuf[0] = '\0'; + break; + case DEV_ID_TYPE_MD_UUID: + read_sys_block(cmd, dev, "md/uuid", sysbuf, sizeof(sysbuf)); +@@ -2524,6 +2537,33 @@ static int _match_dm_names(struct cmd_context *cmd, char *idname, struct device + return 0; + } + ++static void _replace_incorrect_dm_idtype(struct dev_use *du) ++{ ++ /* Previous bug let one of these types be swapped for another ++ because they all read their value from sysfs file dm/uuid. */ ++ if (du->idtype != DEV_ID_TYPE_MPATH_UUID && ++ du->idtype != DEV_ID_TYPE_CRYPT_UUID && ++ du->idtype != DEV_ID_TYPE_LVMLV_UUID) ++ return; ++ ++ /* Use the IDNAME value to determine the correct IDTYPE. */ ++ if (dm_uuid_has_prefix(du->idname, "mpath-") && (du->idtype != DEV_ID_TYPE_MPATH_UUID)) { ++ log_debug("replace incorrect mpath device id type for %u %s", du->idtype, du->idname); ++ du->idtype = DEV_ID_TYPE_MPATH_UUID; ++ return; ++ } ++ if (dm_uuid_has_prefix(du->idname, "CRYPT-") && (du->idtype != DEV_ID_TYPE_CRYPT_UUID)) { ++ log_debug("replace incorrect crypt device id type for %u %s", du->idtype, du->idname); ++ du->idtype = DEV_ID_TYPE_CRYPT_UUID; ++ return; ++ } ++ if (dm_uuid_has_prefix(du->idname, "LVM-") && (du->idtype != DEV_ID_TYPE_LVMLV_UUID)) { ++ log_debug("replace incorrect lvmlv device id type for %u %s", du->idtype, du->idname); ++ du->idtype = DEV_ID_TYPE_LVMLV_UUID; ++ return; ++ } ++} ++ + /* + * du is a devices file entry. dev is any device on the system. + * check if du is for dev by comparing the device's ids to du->idname. +@@ -2634,6 +2674,18 @@ static int _match_du_to_dev(struct cmd_context *cmd, struct dev_use *du, struct + _reduce_repeating_underscores(du_idname, sizeof(du_idname)); + } + ++ /* ++ * A past bug allowed mpath, crypt, or lvm devices to be added to ++ * system.devices using any of the dm id types, and they could be ++ * used normally. Accept an old system.devices with that mistake ++ * by updating du->idtype to have the correct idtype based on the ++ * idname dm prefix. ++ */ ++ if (((du->idtype == DEV_ID_TYPE_MPATH_UUID) && !dm_uuid_has_prefix(du->idname, "mpath-")) || ++ ((du->idtype == DEV_ID_TYPE_CRYPT_UUID) && !dm_uuid_has_prefix(du->idname, "CRYPT-")) || ++ ((du->idtype == DEV_ID_TYPE_LVMLV_UUID) && !dm_uuid_has_prefix(du->idname, "LVM"))) ++ _replace_incorrect_dm_idtype(du); ++ + /* + * Try to match du with ids that have already been read for the dev + * (and saved on dev->ids to avoid rereading.) +-- +2.54.0 + diff --git a/0126-lvmcmdline-fix-line-too-long-check-using-wrong-buffe.patch b/0126-lvmcmdline-fix-line-too-long-check-using-wrong-buffe.patch new file mode 100644 index 0000000..190e447 --- /dev/null +++ b/0126-lvmcmdline-fix-line-too-long-check-using-wrong-buffe.patch @@ -0,0 +1,32 @@ +From 0ecb57749f1bb0971510bf4c1a1692d0a4a51525 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 11:12:02 +0200 +Subject: [PATCH 126/211] lvmcmdline: fix line-too-long check using wrong + buffer index + +The expression buffer[sizeof(buffer) - 1] - 2 performed arithmetic +on the char value at the last position instead of indexing position +sizeof(buffer) - 2 to check for newline. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 79eca4f6a5061c9ec4f544122edd341799d38ec6) +--- + tools/lvmcmdline.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c +index 2ecdbd164..df11f57ab 100644 +--- a/tools/lvmcmdline.c ++++ b/tools/lvmcmdline.c +@@ -3526,7 +3526,7 @@ static int _run_script(struct cmd_context *cmd, int argc, char **argv) + } + } + if ((strlen(buffer) == sizeof(buffer) - 1) +- && (buffer[sizeof(buffer) - 1] - 2 != '\n')) { ++ && (buffer[sizeof(buffer) - 2] != '\n')) { + buffer[50] = '\0'; + log_error("Line too long (max 255) beginning: %s", + buffer); +-- +2.54.0 + diff --git a/0127-report-fix-lvseg_metadata_devices_str-calling-wrong-.patch b/0127-report-fix-lvseg_metadata_devices_str-calling-wrong-.patch new file mode 100644 index 0000000..33409f9 --- /dev/null +++ b/0127-report-fix-lvseg_metadata_devices_str-calling-wrong-.patch @@ -0,0 +1,32 @@ +From 007326d4b11a22bc27e3e96ebd45d63f009ac82a Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 08:33:48 +0200 +Subject: [PATCH 127/211] report: fix lvseg_metadata_devices_str calling wrong + function + +lvseg_metadata_devices_str() was calling lvseg_devices() instead +of lvseg_metadata_devices(), causing the metadata_devices report +field to return data devices instead of metadata devices. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit aba1e6f1aed9cf2cf22ba53b54f06462107426b7) +--- + lib/metadata/lv.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c +index d201244a7..164ad7aba 100644 +--- a/lib/metadata/lv.c ++++ b/lib/metadata/lv.c +@@ -158,7 +158,7 @@ char *lvseg_metadata_devices_str(struct dm_pool *mem, const struct lv_segment *s + { + struct dm_list *list; + +- if (!(list = lvseg_devices(mem, seg))) ++ if (!(list = lvseg_metadata_devices(mem, seg))) + return_NULL; + + return str_list_to_str(mem, list, ","); +-- +2.54.0 + diff --git a/0128-report-fix-lvseg_seg_le_ranges_str-calling-wrong-fun.patch b/0128-report-fix-lvseg_seg_le_ranges_str-calling-wrong-fun.patch new file mode 100644 index 0000000..65de3fe --- /dev/null +++ b/0128-report-fix-lvseg_seg_le_ranges_str-calling-wrong-fun.patch @@ -0,0 +1,31 @@ +From 9e70f14abecee521f3036d0a8bca90646f8f83c2 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 08:34:15 +0200 +Subject: [PATCH 128/211] report: fix lvseg_seg_le_ranges_str calling wrong + function + +lvseg_seg_le_ranges_str() was calling lvseg_seg_pe_ranges() instead +of lvseg_seg_le_ranges(), missing the report_mark_hidden_devices flag. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 826632ee8890fd9305f270f6c3ee09ad4fd8422c) +--- + lib/metadata/lv.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c +index 164ad7aba..7476cef8d 100644 +--- a/lib/metadata/lv.c ++++ b/lib/metadata/lv.c +@@ -188,7 +188,7 @@ char *lvseg_seg_le_ranges_str(struct dm_pool *mem, const struct lv_segment *seg) + { + struct dm_list *list; + +- if (!(list = lvseg_seg_pe_ranges(mem, seg))) ++ if (!(list = lvseg_seg_le_ranges(mem, seg))) + return_NULL; + + return str_list_to_str(mem, list, seg->lv->vg->cmd->report_list_item_separator); +-- +2.54.0 + diff --git a/0129-vgchange-add-NULL-check-for-vg-system_id-before-strc.patch b/0129-vgchange-add-NULL-check-for-vg-system_id-before-strc.patch new file mode 100644 index 0000000..98df4c2 --- /dev/null +++ b/0129-vgchange-add-NULL-check-for-vg-system_id-before-strc.patch @@ -0,0 +1,31 @@ +From 84f108796ba80141465c035de75d36ac52a08b23 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 11:42:41 +0200 +Subject: [PATCH 129/211] vgchange: add NULL check for vg->system_id before + strcmp + +vg->system_id can be NULL (set in vg.c and metadata.c), so +strcmp without a NULL check may cause undefined behavior. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 0e95d6933e2901e765a34d849f70ad718af66cf5) +--- + tools/vgchange.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/vgchange.c b/tools/vgchange.c +index deefbfe6d..2ffd73ca1 100644 +--- a/tools/vgchange.c ++++ b/tools/vgchange.c +@@ -559,7 +559,7 @@ static int _vgchange_system_id(struct cmd_context *cmd, struct volume_group *vg) + return 0; + } + +- if (!strcmp(vg->system_id, system_id)) { ++ if (vg->system_id && !strcmp(vg->system_id, system_id)) { + log_error("Volume Group system ID is already \"%s\".", vg->system_id); + return 0; + } +-- +2.54.0 + diff --git a/0130-libdm-iface-use-st_rdev-instead-of-st_mode-for-MAJOR.patch b/0130-libdm-iface-use-st_rdev-instead-of-st_mode-for-MAJOR.patch new file mode 100644 index 0000000..149e193 --- /dev/null +++ b/0130-libdm-iface-use-st_rdev-instead-of-st_mode-for-MAJOR.patch @@ -0,0 +1,34 @@ +From 6adbfc16d1b8ba3d15bb7dc5d0e1998bb75349a5 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 11:59:23 +0200 +Subject: [PATCH 130/211] libdm: iface: use st_rdev instead of st_mode for + MAJOR/MINOR + +The log message was extracting major/minor from buf.st_mode +(file type/permissions) instead of buf.st_rdev (device number). +The comparison on line above correctly uses buf.st_rdev. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 42fb34bcf836c3356bf8f23d24bac2fe34d35346) +--- + libdm/ioctl/libdm-iface.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/libdm/ioctl/libdm-iface.c b/libdm/ioctl/libdm-iface.c +index 3a3060475..4bbb40a7d 100644 +--- a/libdm/ioctl/libdm-iface.c ++++ b/libdm/ioctl/libdm-iface.c +@@ -278,8 +278,8 @@ static int _control_exists(const char *control, uint32_t major, uint32_t minor) + + if (major && buf.st_rdev != MKDEV(major, minor)) { + log_verbose("%s: Wrong device number: (%u, %u) instead of " +- "(%u, %u)", control, +- MAJOR(buf.st_mode), MINOR(buf.st_mode), ++ "(%u, %u).", control, ++ MAJOR(buf.st_rdev), MINOR(buf.st_rdev), + major, minor); + return _control_unlink(control); + } +-- +2.54.0 + diff --git a/0131-lvmcache-fix-unnecessary-address-of-on-array-in-memc.patch b/0131-lvmcache-fix-unnecessary-address-of-on-array-in-memc.patch new file mode 100644 index 0000000..1f0add5 --- /dev/null +++ b/0131-lvmcache-fix-unnecessary-address-of-on-array-in-memc.patch @@ -0,0 +1,31 @@ +From c238b6ccc110030a272b1d07e229edc2e0db28e6 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 01:25:37 +0200 +Subject: [PATCH 131/211] lvmcache: fix unnecessary address-of on array in + memcmp + +Remove extra '&' before 'pvid' array in _get_pvsummary_device_id +memcmp call, matching the style of the other two pvsummary functions. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 27afb397ff88c514cf21a4b02a1e04f5c04a8332) +--- + lib/cache/lvmcache.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/cache/lvmcache.c b/lib/cache/lvmcache.c +index 1effc254c..4c95db7db 100644 +--- a/lib/cache/lvmcache.c ++++ b/lib/cache/lvmcache.c +@@ -524,7 +524,7 @@ static const char *_get_pvsummary_device_id(const char *pvid_arg, const char **d + + dm_list_iterate_items(vginfo, &_vginfos) { + dm_list_iterate_items(pvl, &vginfo->pvsummaries) { +- if (!memcmp(&pvid, &pvl->pv->id.uuid, ID_LEN)) { ++ if (!memcmp(pvid, &pvl->pv->id.uuid, ID_LEN)) { + *device_id_type = pvl->pv->device_id_type; + return pvl->pv->device_id; + } +-- +2.54.0 + diff --git a/0132-RHEL9-fix-compilation-issues.patch b/0132-RHEL9-fix-compilation-issues.patch new file mode 100644 index 0000000..b516d7c --- /dev/null +++ b/0132-RHEL9-fix-compilation-issues.patch @@ -0,0 +1,115 @@ +From 444317ac2c2c6d40271c1e4ced0c95726c925de5 Mon Sep 17 00:00:00 2001 +From: Marian Csontos +Date: Fri, 15 May 2026 10:34:34 +0200 +Subject: [PATCH 132/211] RHEL9: fix compilation issues + +--- + lib/device/device_id.c | 18 +++++++++--------- + lib/metadata/lv.c | 2 +- + lib/report/report.c | 2 +- + tools/reporter.c | 2 +- + 4 files changed, 12 insertions(+), 12 deletions(-) + +diff --git a/lib/device/device_id.c b/lib/device/device_id.c +index 91c998fe2..a83257e50 100644 +--- a/lib/device/device_id.c ++++ b/lib/device/device_id.c +@@ -847,19 +847,19 @@ char *device_id_system_read(struct cmd_context *cmd, struct device *dev, uint16_ + case DEV_ID_TYPE_MPATH_UUID: + if (!dev_dm_uuid(cmd, dev, sysbuf, sizeof(sysbuf))) + stack; +- if (sysbuf[0] && !dm_uuid_has_prefix(sysbuf, "mpath-")) ++ if (sysbuf[0] && !_dm_uuid_has_prefix(sysbuf, "mpath-")) + sysbuf[0] = '\0'; + break; + case DEV_ID_TYPE_CRYPT_UUID: + if (!dev_dm_uuid(cmd, dev, sysbuf, sizeof(sysbuf))) + stack; +- if (sysbuf[0] && !dm_uuid_has_prefix(sysbuf, "CRYPT-")) ++ if (sysbuf[0] && !_dm_uuid_has_prefix(sysbuf, "CRYPT-")) + sysbuf[0] = '\0'; + break; + case DEV_ID_TYPE_LVMLV_UUID: + if (!dev_dm_uuid(cmd, dev, sysbuf, sizeof(sysbuf))) + stack; +- if (sysbuf[0] && !dm_uuid_has_prefix(sysbuf, "LVM-")) ++ if (sysbuf[0] && !_dm_uuid_has_prefix(sysbuf, "LVM-")) + sysbuf[0] = '\0'; + break; + case DEV_ID_TYPE_MD_UUID: +@@ -2547,17 +2547,17 @@ static void _replace_incorrect_dm_idtype(struct dev_use *du) + return; + + /* Use the IDNAME value to determine the correct IDTYPE. */ +- if (dm_uuid_has_prefix(du->idname, "mpath-") && (du->idtype != DEV_ID_TYPE_MPATH_UUID)) { ++ if (_dm_uuid_has_prefix(du->idname, "mpath-") && (du->idtype != DEV_ID_TYPE_MPATH_UUID)) { + log_debug("replace incorrect mpath device id type for %u %s", du->idtype, du->idname); + du->idtype = DEV_ID_TYPE_MPATH_UUID; + return; + } +- if (dm_uuid_has_prefix(du->idname, "CRYPT-") && (du->idtype != DEV_ID_TYPE_CRYPT_UUID)) { ++ if (_dm_uuid_has_prefix(du->idname, "CRYPT-") && (du->idtype != DEV_ID_TYPE_CRYPT_UUID)) { + log_debug("replace incorrect crypt device id type for %u %s", du->idtype, du->idname); + du->idtype = DEV_ID_TYPE_CRYPT_UUID; + return; + } +- if (dm_uuid_has_prefix(du->idname, "LVM-") && (du->idtype != DEV_ID_TYPE_LVMLV_UUID)) { ++ if (_dm_uuid_has_prefix(du->idname, "LVM-") && (du->idtype != DEV_ID_TYPE_LVMLV_UUID)) { + log_debug("replace incorrect lvmlv device id type for %u %s", du->idtype, du->idname); + du->idtype = DEV_ID_TYPE_LVMLV_UUID; + return; +@@ -2681,9 +2681,9 @@ static int _match_du_to_dev(struct cmd_context *cmd, struct dev_use *du, struct + * by updating du->idtype to have the correct idtype based on the + * idname dm prefix. + */ +- if (((du->idtype == DEV_ID_TYPE_MPATH_UUID) && !dm_uuid_has_prefix(du->idname, "mpath-")) || +- ((du->idtype == DEV_ID_TYPE_CRYPT_UUID) && !dm_uuid_has_prefix(du->idname, "CRYPT-")) || +- ((du->idtype == DEV_ID_TYPE_LVMLV_UUID) && !dm_uuid_has_prefix(du->idname, "LVM"))) ++ if (((du->idtype == DEV_ID_TYPE_MPATH_UUID) && !_dm_uuid_has_prefix(du->idname, "mpath-")) || ++ ((du->idtype == DEV_ID_TYPE_CRYPT_UUID) && !_dm_uuid_has_prefix(du->idname, "CRYPT-")) || ++ ((du->idtype == DEV_ID_TYPE_LVMLV_UUID) && !_dm_uuid_has_prefix(du->idname, "LVM"))) + _replace_incorrect_dm_idtype(du); + + /* +diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c +index 7476cef8d..0917cdd4e 100644 +--- a/lib/metadata/lv.c ++++ b/lib/metadata/lv.c +@@ -1552,7 +1552,7 @@ char *lv_attr_dup_with_info_and_seg_status(struct dm_pool *mem, const struct lv_ + + if (!activation()) + repstr[8] = 'X'; /* Unknown */ +- else if (!lv_raid_healthy(lv)) ++ else if (!lv_raid_healthy(lv, NULL)) + repstr[8] = 'r'; /* RAID needs 'r'efresh */ + else if (lv_is_raid(lv)) { + if (lv_raid_mismatch_count(lv, &n) && n) +diff --git a/lib/report/report.c b/lib/report/report.c +index 049accf41..ab0675f69 100644 +--- a/lib/report/report.c ++++ b/lib/report/report.c +@@ -4005,7 +4005,7 @@ static int _lvhealthstatus_disp(struct dm_report *rh, struct dm_pool *mem, + else if (lv_is_raid_type(lv)) { + if (!activation()) + health = "unknown"; +- else if (!lv_raid_healthy(lv)) ++ else if (!lv_raid_healthy(lv, NULL)) + health = "refresh needed"; + else if (lv_is_raid(lv)) { + if (lv_raid_mismatch_count(lv, &n) && n) +diff --git a/tools/reporter.c b/tools/reporter.c +index e88b8fa72..f4255680c 100644 +--- a/tools/reporter.c ++++ b/tools/reporter.c +@@ -155,7 +155,7 @@ static int _check_merging_origin(const struct logical_volume *lv, + + static void _cond_warn_raid_volume_health(struct cmd_context *cmd, const struct logical_volume *lv) + { +- if (lv_is_raid(lv) && !lv_raid_healthy(lv) && !lv_is_partial(lv)) ++ if (lv_is_raid(lv) && !lv_raid_healthy(lv, NULL) && !lv_is_partial(lv)) + log_warn("WARNING: RaidLV %s needs to be refreshed! See character 'r' at position 9 in the RaidLV's attributes%s.", display_lvname(lv), + arg_is_set(cmd, all_ARG) ? " and its SubLV(s)" : " and also its SubLV(s) with option '-a'"); + } +-- +2.54.0 + diff --git a/0133-cov-command-fix-NULL-dereference-in-_add_oo_definiti.patch b/0133-cov-command-fix-NULL-dereference-in-_add_oo_definiti.patch new file mode 100644 index 0000000..49f44c0 --- /dev/null +++ b/0133-cov-command-fix-NULL-dereference-in-_add_oo_definiti.patch @@ -0,0 +1,37 @@ +From 4cd7e5b0d9d00995153702a068a02c643b079b5f Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Wed, 1 Apr 2026 22:37:32 +0200 +Subject: [PATCH 133/211] cov: command: fix NULL dereference in + _add_oo_definition_line + +Check strchr return value before adding offset. If the line +has no colon, strchr returns NULL and the +2 produces an +invalid pointer passed to strdup. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit a794299bb03fbd88f35780ad9005060a2b558804) +--- + tools/command.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/tools/command.c b/tools/command.c +index db1058bd7..519bb7eb3 100644 +--- a/tools/command.c ++++ b/tools/command.c +@@ -667,7 +667,12 @@ static void _add_oo_definition_line(const char *name, const char *line) + return; + } + +- start = strchr(line, ':') + 2; ++ if (!(start = strchr(line, ':'))) { ++ log_error("Parsing command defs: invalid OO line."); ++ return; ++ } ++ start += 2; ++ + if (!(oo->line = strdup(start))) { + log_error("Failed to duplicate line %s.", start); + return; +-- +2.54.0 + diff --git a/0134-bcache-fix-flush-orphaning-locked-dirty-blocks.patch b/0134-bcache-fix-flush-orphaning-locked-dirty-blocks.patch new file mode 100644 index 0000000..e831a38 --- /dev/null +++ b/0134-bcache-fix-flush-orphaning-locked-dirty-blocks.patch @@ -0,0 +1,52 @@ +From 2742b532fbe93dc2d19725031ebc85cdbcac36f5 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 10:16:17 +0200 +Subject: [PATCH 134/211] bcache: fix flush orphaning locked dirty blocks + +bcache_flush used _list_pop to remove blocks from the dirty list +before checking ref_count. Blocks still locked (e.g. superblock) +were popped but never re-added to any list, orphaning them. + +Switch to dm_list_iterate_items_gen_safe matching the pattern +used by _writeback(). _issue_write() internally moves written +blocks from dirty to io_pending via dm_list_move(), while locked +blocks are skipped and remain on the dirty list. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit b81b48881d4af2bc28dff257ccbf583b129b9204) +--- + lib/device/bcache.c | 18 +++++++++--------- + 1 file changed, 9 insertions(+), 9 deletions(-) + +diff --git a/lib/device/bcache.c b/lib/device/bcache.c +index c61f7a5d5..afa0315fd 100644 +--- a/lib/device/bcache.c ++++ b/lib/device/bcache.c +@@ -1294,15 +1294,15 @@ bool bcache_flush(struct bcache *cache) + // try and rewrite everything. + dm_list_splice(&cache->dirty, &cache->errored); + +- while (!dm_list_empty(&cache->dirty)) { +- struct block *b = dm_list_item(_list_pop(&cache->dirty), struct block); +- if (b->ref_count || _test_flags(b, BF_IO_PENDING)) { +- // The superblock may well be still locked. +- continue; +- } +- +- _issue_write(b); +- } ++ /* ++ * _writeback() uses safe iteration and skips locked blocks ++ * (ref_count > 0). _issue_write() moves written blocks from ++ * dirty to io_pending via dm_list_move() in _issue_low_level(). ++ * BF_IO_PENDING cannot occur here - _issue_low_level() moves ++ * the block off dirty when setting that flag, and _complete_io() ++ * clears it before moving to errored/clean. ++ */ ++ _writeback(cache, cache->nr_cache_blocks); + + _wait_all(cache); + +-- +2.54.0 + diff --git a/0135-dmeventd-fix-inverted-systemd-activation-env-var-che.patch b/0135-dmeventd-fix-inverted-systemd-activation-env-var-che.patch new file mode 100644 index 0000000..f4262fe --- /dev/null +++ b/0135-dmeventd-fix-inverted-systemd-activation-env-var-che.patch @@ -0,0 +1,32 @@ +From 12de2eed0e6bcf34f3f019b53fd7a412299ca429 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 12:41:41 +0200 +Subject: [PATCH 135/211] dmeventd: fix inverted systemd activation env var + check + +strcmp(e, "1") returns 0 (false) when strings match, so the condition +was setting _systemd_activation when env var was NOT "1" and skipping +it when it WAS "1" -- the exact opposite of the intended behavior. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 6c0aad9e0f8e844eeae4350d79ba2674c7c4342d) +--- + daemons/dmeventd/dmeventd.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/daemons/dmeventd/dmeventd.c b/daemons/dmeventd/dmeventd.c +index 0c3880787..1ae5ef629 100644 +--- a/daemons/dmeventd/dmeventd.c ++++ b/daemons/dmeventd/dmeventd.c +@@ -2233,7 +2233,7 @@ static int _restart_dmeventd(struct dm_event_fifos *fifos) + } + + if (!_systemd_activation && +- ((e = getenv(SD_ACTIVATION_ENV_VAR_NAME)) && strcmp(e, "1"))) ++ ((e = getenv(SD_ACTIVATION_ENV_VAR_NAME)) && !strcmp(e, "1"))) + _systemd_activation = 1; + + fini_fifos(fifos); +-- +2.54.0 + diff --git a/0136-lvrename-fix-historical-LV-prefix-stripping-using-wr.patch b/0136-lvrename-fix-historical-LV-prefix-stripping-using-wr.patch new file mode 100644 index 0000000..994b6df --- /dev/null +++ b/0136-lvrename-fix-historical-LV-prefix-stripping-using-wr.patch @@ -0,0 +1,32 @@ +From b0a6b406e5ca1eec0727192193f6d6c2566e15f3 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 11:27:21 +0200 +Subject: [PATCH 136/211] lvrename: fix historical LV prefix stripping using + wrong variable + +When both old and new names have the historical prefix, the prefix +was stripped from lv_name_old instead of lv_name_new, leaving +lv_name_new with the prefix still attached. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 067fa3eb938bbb808978dcf21ac62236a0dff45b) +--- + tools/lvrename.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/lvrename.c b/tools/lvrename.c +index 74248ee91..6fcf1db53 100644 +--- a/tools/lvrename.c ++++ b/tools/lvrename.c +@@ -165,7 +165,7 @@ int lvrename(struct cmd_context *cmd, int argc, char **argv) + + if (!strncmp(lv_name_new, HISTORICAL_LV_PREFIX, strlen(HISTORICAL_LV_PREFIX))) { + if (historical) +- lv_name_new = lv_name_old + strlen(HISTORICAL_LV_PREFIX); ++ lv_name_new = lv_name_new + strlen(HISTORICAL_LV_PREFIX); + else { + log_error("Old name references live LV while " + "new name is for historical LV."); +-- +2.54.0 + diff --git a/0137-lvmdevices-fix-resource-leak-on-delpvid-duplicate-er.patch b/0137-lvmdevices-fix-resource-leak-on-delpvid-duplicate-er.patch new file mode 100644 index 0000000..a253edf --- /dev/null +++ b/0137-lvmdevices-fix-resource-leak-on-delpvid-duplicate-er.patch @@ -0,0 +1,30 @@ +From 2a3765160281eda24e7a1aaa5089f86bc49fc688 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 11:35:25 +0200 +Subject: [PATCH 137/211] lvmdevices: fix resource leak on delpvid duplicate + error path + +When a duplicate PVID was found, du was already removed from the +list but never freed before goto bad, leaking the structure. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit d0f9bf1c9f4438a369087edf6e51147b844ae336) +--- + tools/lvmdevices.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/tools/lvmdevices.c b/tools/lvmdevices.c +index 65399c96c..78ff70455 100644 +--- a/tools/lvmdevices.c ++++ b/tools/lvmdevices.c +@@ -1010,6 +1010,7 @@ int lvmdevices(struct cmd_context *cmd, int argc, char **argv) + if ((du2 = get_du_for_pvid(cmd, pvid))) { + log_error("Multiple devices file entries for PVID %s (%s %s), remove by device name.", + pvid, du->devname, du2->devname); ++ free_du(du); + goto bad; + } + +-- +2.54.0 + diff --git a/0138-dev-cache-fix-missing-dev_iter_destroy-in-iterate_de.patch b/0138-dev-cache-fix-missing-dev_iter_destroy-in-iterate_de.patch new file mode 100644 index 0000000..40e90e3 --- /dev/null +++ b/0138-dev-cache-fix-missing-dev_iter_destroy-in-iterate_de.patch @@ -0,0 +1,37 @@ +From 8ae8f80d5234875e22a753b664f8ffa77754fb20 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 09:04:35 +0200 +Subject: [PATCH 138/211] dev-cache: fix missing dev_iter_destroy in + iterate_devs_for_index + +Add missing dev_iter_destroy() call before return in +_dev_cache_iterate_devs_for_index(). + +The leak only occurs on the udev scanning path - the non-udev sysfs +fallback (_dev_cache_iterate_sysfs_for_index) reads sysfs directly +without using an iterator. Since the function is called once per +command, the leak is small (iterator struct + values array), which +explains why it was not caught in normal testing. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 9d98bf8a448a58d56417daa127b8cd5002184c00) +--- + lib/device/dev-cache.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/lib/device/dev-cache.c b/lib/device/dev-cache.c +index 24a4d91c5..8f9ef1bd7 100644 +--- a/lib/device/dev-cache.c ++++ b/lib/device/dev-cache.c +@@ -912,6 +912,8 @@ static int _dev_cache_iterate_devs_for_index(struct cmd_context *cmd) + if (!_index_dev_by_vgid_and_lvid(cmd, dev)) + r = 0; + ++ dev_iter_destroy(iter); ++ + return r; + } + +-- +2.54.0 + diff --git a/0139-import_vsn1-fix-resource-leak-on-historical-LV-id-re.patch b/0139-import_vsn1-fix-resource-leak-on-historical-LV-id-re.patch new file mode 100644 index 0000000..b2c113e --- /dev/null +++ b/0139-import_vsn1-fix-resource-leak-on-historical-LV-id-re.patch @@ -0,0 +1,30 @@ +From 1b21775c5b6d64ecc7e2acde34eac00abe4a5bb7 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 10:07:28 +0200 +Subject: [PATCH 139/211] import_vsn1: fix resource leak on historical LV id + read failure + +Use goto bad instead of return 0 to properly free allocations. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit dd4fd687ca3042d0c35b7a134cdb3eaaefaeec79) +--- + lib/format_text/import_vsn1.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/format_text/import_vsn1.c b/lib/format_text/import_vsn1.c +index 4f76625a5..20b1739e9 100644 +--- a/lib/format_text/import_vsn1.c ++++ b/lib/format_text/import_vsn1.c +@@ -792,7 +792,7 @@ static int _read_historical_lvnames(struct cmd_context *cmd, + if (!_read_id(&glv->historical->lvid.id[1], hlvn, "id")) { + log_error("Couldn't read uuid for removed logical volume %s in vg %s.", + glv->historical->name, vg->name); +- return 0; ++ goto bad; + } + memcpy(&glv->historical->lvid.id[0], &glv->historical->vg->id, sizeof(glv->historical->lvid.id[0])); + +-- +2.54.0 + diff --git a/0140-lvm-exec-fix-close-error-msg-and-null_fd-fd-case.patch b/0140-lvm-exec-fix-close-error-msg-and-null_fd-fd-case.patch new file mode 100644 index 0000000..a1b77d1 --- /dev/null +++ b/0140-lvm-exec-fix-close-error-msg-and-null_fd-fd-case.patch @@ -0,0 +1,36 @@ +From 8dd9169a9128aca196b2b53bd7ede956bc24cd70 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 13:07:54 +0200 +Subject: [PATCH 140/211] lvm-exec: fix close error msg and null_fd == fd case + +1. Fix copy-paste error in log_sys_error: reported "dup2" instead of + "close" for the close(null_fd) call. + +2. Guard close(null_fd) with null_fd != fd check. When the target fd + was already closed, open("/dev/null") reuses the same fd number, + dup2 is a no-op, and unconditional close would undo the setup. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 0139987d8d200ae2688fba2029af6c52f441f632) +--- + lib/misc/lvm-exec.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/lib/misc/lvm-exec.c b/lib/misc/lvm-exec.c +index 37b392545..5ae33516c 100644 +--- a/lib/misc/lvm-exec.c ++++ b/lib/misc/lvm-exec.c +@@ -136,8 +136,8 @@ static int _reopen_fd_to_null(int fd) + + r = 1; + out: +- if (close(null_fd)) { +- log_sys_error("dup2", ""); ++ if ((null_fd != fd) && close(null_fd)) { ++ log_sys_error("close", "/dev/null"); + return 0; + } + +-- +2.54.0 + diff --git a/0141-lvmlockctl-fix-close-error-msg-and-null_fd-fd-case.patch b/0141-lvmlockctl-fix-close-error-msg-and-null_fd-fd-case.patch new file mode 100644 index 0000000..2ddd181 --- /dev/null +++ b/0141-lvmlockctl-fix-close-error-msg-and-null_fd-fd-case.patch @@ -0,0 +1,43 @@ +From 159c89d0d33161e18dd75a53891528747313c62e Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Apr 2026 18:09:34 +0200 +Subject: [PATCH 141/211] lvmlockctl: fix close error msg and null_fd == fd + case + +Add missing null_fd != fd guards in _reopen_fd_to_null to match +lib/misc/lvm-exec.c implementation. Without these guards, if +open("/dev/null") returns the same fd number as the target fd +(e.g., fd 0 is already closed so open returns 0), the function +would incorrectly close the just-opened fd before dup2. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit f5ca64812df94d7816f06cfa383a0b06086105e1) +--- + daemons/lvmlockd/lvmlockctl.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/daemons/lvmlockd/lvmlockctl.c b/daemons/lvmlockd/lvmlockctl.c +index e36293260..63b7f77c6 100644 +--- a/daemons/lvmlockd/lvmlockctl.c ++++ b/daemons/lvmlockd/lvmlockctl.c +@@ -726,7 +726,7 @@ static int _reopen_fd_to_null(int fd) + return 0; + } + +- if (close(fd)) { ++ if ((null_fd != fd) && close(fd)) { + log_error("close error fd %d %d", fd, errno); + goto out; + } +@@ -738,7 +738,7 @@ static int _reopen_fd_to_null(int fd) + + r = 1; + out: +- if (close(null_fd)) { ++ if ((null_fd != fd) && close(null_fd)) { + log_error("close error fd %d %d", null_fd, errno); + return 0; + } +-- +2.54.0 + diff --git a/0142-bcache-do-not-call-io_destroy-in-forked-process.patch b/0142-bcache-do-not-call-io_destroy-in-forked-process.patch new file mode 100644 index 0000000..b995eba --- /dev/null +++ b/0142-bcache-do-not-call-io_destroy-in-forked-process.patch @@ -0,0 +1,60 @@ +From 82ab9fbd220d8f1461397dafbbbbc59d6fc15ba5 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 3 Oct 2025 20:14:31 +0200 +Subject: [PATCH 142/211] bcache: do not call io_destroy in forked process + +(cherry picked from commit 2b632d5f3a3fad50e3e39a89af58bd62d3a2d7e3) +--- + lib/device/bcache.c | 18 +++++++++++++----- + 1 file changed, 13 insertions(+), 5 deletions(-) + +diff --git a/lib/device/bcache.c b/lib/device/bcache.c +index afa0315fd..b66c5fa7c 100644 +--- a/lib/device/bcache.c ++++ b/lib/device/bcache.c +@@ -131,6 +131,7 @@ struct async_engine { + io_context_t aio_context; + struct cb_set *cbs; + unsigned page_mask; ++ pid_t aio_context_pid; /* PID that created this AIO context */ + }; + + static struct async_engine *_to_async(struct io_engine *e) +@@ -140,15 +141,21 @@ static struct async_engine *_to_async(struct io_engine *e) + + static void _async_destroy(struct io_engine *ioe) + { +- int r; + struct async_engine *e = _to_async(ioe); + + _cb_set_destroy(e->cbs); + +- // io_destroy is really slow +- r = io_destroy(e->aio_context); +- if (r) +- log_sys_warn("io_destroy"); ++ /* ++ * Only call io_destroy() if we're in the same process that created ++ * the AIO context. After fork(), the child inherits the parent's ++ * aio_context value but must not call io_destroy() on it. ++ */ ++ if (e->aio_context) { ++ if (e->aio_context_pid != getpid()) ++ log_debug("Skipping io_destroy() for different pid."); ++ else if (io_destroy(e->aio_context)) // really slow ++ log_sys_warn("io_destroy"); ++ } + + free(e); + } +@@ -376,6 +383,7 @@ struct io_engine *create_async_io_engine(void) + e->e.max_io = _async_max_io; + + e->aio_context = 0; ++ e->aio_context_pid = getpid(); + r = io_setup(MAX_IO, &e->aio_context); + if (r < 0) { + log_debug("io_setup failed %d", r); +-- +2.54.0 + diff --git a/0143-debug-add-tracing-for-context-destroy.patch b/0143-debug-add-tracing-for-context-destroy.patch new file mode 100644 index 0000000..ac4e69c --- /dev/null +++ b/0143-debug-add-tracing-for-context-destroy.patch @@ -0,0 +1,36 @@ +From f4cbf26376cea75db8d1ed8cfeebc11a94693c9f Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Mon, 8 Dec 2025 14:44:49 +0100 +Subject: [PATCH 143/211] debug: add tracing for context destroy + +Add trace note where the time is being lost. +Context destroy may take ~40ms in kernel. + +(cherry picked from commit f1374c5876783301b26812b144d2d016bb5f519f) +--- + lib/device/bcache.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/lib/device/bcache.c b/lib/device/bcache.c +index b66c5fa7c..915db250c 100644 +--- a/lib/device/bcache.c ++++ b/lib/device/bcache.c +@@ -152,9 +152,12 @@ static void _async_destroy(struct io_engine *ioe) + */ + if (e->aio_context) { + if (e->aio_context_pid != getpid()) +- log_debug("Skipping io_destroy() for different pid."); +- else if (io_destroy(e->aio_context)) // really slow +- log_sys_warn("io_destroy"); ++ log_debug_devs("Skipping AIO context destroy for different pid."); ++ else { ++ log_debug_devs("Destroy AIO context."); ++ if (io_destroy(e->aio_context)) // really slow (~40ms) ++ log_sys_warn("io_destroy"); ++ } + } + + free(e); +-- +2.54.0 + diff --git a/0144-bcache-report-libaio-errors-properly.patch b/0144-bcache-report-libaio-errors-properly.patch new file mode 100644 index 0000000..77ffcfc --- /dev/null +++ b/0144-bcache-report-libaio-errors-properly.patch @@ -0,0 +1,62 @@ +From 884d5e0dd0afca46d2f72b9448a6fa9e1cddf1c1 Mon Sep 17 00:00:00 2001 +From: Mike Gilbert +Date: Fri, 13 Feb 2026 21:47:12 -0500 +Subject: [PATCH 144/211] bcache: report libaio errors properly + +io_getevents and io_destroy return a negative error value instead of +setting errno. + +Bug: https://bugs.gentoo.org/970017 +(cherry picked from commit 512a3944893af8c1d4ce69c69d2ae587aebf2b08) +--- + lib/device/bcache.c | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +diff --git a/lib/device/bcache.c b/lib/device/bcache.c +index 915db250c..2ecad6376 100644 +--- a/lib/device/bcache.c ++++ b/lib/device/bcache.c +@@ -40,9 +40,9 @@ static int *_fd_table = NULL; + + //---------------------------------------------------------------- + +-static void log_sys_warn(const char *call) ++static void log_sys_warn(const char *call, int err) + { +- log_warn("WARNING: %s failed: %s.", call, strerror(errno)); ++ log_warn("WARNING: %s failed: %s.", call, strerror(err)); + } + + // Assumes the list is not empty. +@@ -141,6 +141,7 @@ static struct async_engine *_to_async(struct io_engine *e) + + static void _async_destroy(struct io_engine *ioe) + { ++ int r; + struct async_engine *e = _to_async(ioe); + + _cb_set_destroy(e->cbs); +@@ -155,8 +156,9 @@ static void _async_destroy(struct io_engine *ioe) + log_debug_devs("Skipping AIO context destroy for different pid."); + else { + log_debug_devs("Destroy AIO context."); +- if (io_destroy(e->aio_context)) // really slow (~40ms) +- log_sys_warn("io_destroy"); ++ r = io_destroy(e->aio_context); // really slow (~40ms) ++ if (r < 0) ++ log_sys_warn("io_destroy", -r); + } + } + +@@ -331,7 +333,7 @@ static bool _async_wait(struct io_engine *ioe, io_complete_fn fn) + r = io_getevents(e->aio_context, 1, MAX_EVENT, event, NULL); + + if (r < 0) { +- log_sys_warn("io_getevents"); ++ log_sys_warn("io_getevents", -r); + return false; + } + +-- +2.54.0 + diff --git a/0145-bcache-retry-io_getevents-on-EINTR.patch b/0145-bcache-retry-io_getevents-on-EINTR.patch new file mode 100644 index 0000000..4b6af0d --- /dev/null +++ b/0145-bcache-retry-io_getevents-on-EINTR.patch @@ -0,0 +1,30 @@ +From 895b49acb8fd5a10d0bb7ae243fe430386aee0a2 Mon Sep 17 00:00:00 2001 +From: Mike Gilbert +Date: Fri, 13 Feb 2026 22:03:32 -0500 +Subject: [PATCH 145/211] bcache: retry io_getevents on EINTR + +Bug: https://bugs.gentoo.org/970017 +(cherry picked from commit 791a842dea11808d647c1a605ca244d0fc1cdf5d) +--- + lib/device/bcache.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/lib/device/bcache.c b/lib/device/bcache.c +index 2ecad6376..4c91402c4 100644 +--- a/lib/device/bcache.c ++++ b/lib/device/bcache.c +@@ -330,7 +330,10 @@ static bool _async_wait(struct io_engine *ioe, io_complete_fn fn) + struct async_engine *e = _to_async(ioe); + + memset(&event, 0, sizeof(event)); +- r = io_getevents(e->aio_context, 1, MAX_EVENT, event, NULL); ++ ++ do { ++ r = io_getevents(e->aio_context, 1, MAX_EVENT, event, NULL); ++ } while (r == -EINTR); + + if (r < 0) { + log_sys_warn("io_getevents", -r); +-- +2.54.0 + diff --git a/0146-bcache-retry-io_getevents-only-if-sigint-not-caught.patch b/0146-bcache-retry-io_getevents-only-if-sigint-not-caught.patch new file mode 100644 index 0000000..812f481 --- /dev/null +++ b/0146-bcache-retry-io_getevents-only-if-sigint-not-caught.patch @@ -0,0 +1,56 @@ +From 424b80266e954f6d612c23121325a891a2a8eec3 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sat, 21 Feb 2026 10:31:45 +0100 +Subject: [PATCH 146/211] bcache: retry io_getevents() only if sigint not + caught + +Stray signals (SIGALRM, SIGCHLD, SIGWINCH, etc.) cause EINTR +but should not abort I/O - the loop retries transparently. +SIGINT/SIGTERM delivered inside a sigint_allow()/sigint_restore() +window set sigint_caught(), which stops the loop and lets the +caller detect and handle the cancellation. + +LVM supports io_getevents() interruptible in commit b3c7a2b3f0bc. +However when read-only command do not use signal handlers, +this sigint_caught() check is effectively ignored. But in this case +command will be interrupted with the default signal handler anyway. + +Co-Authored-By: Claude Sonnet 4.6 + +diff --git a/lib/device/bcache.c b/lib/device/bcache.c + +(cherry picked from commit 0a55dd6dcdcad64a103f1d5ce29a0f1909548b7a) +--- + lib/device/bcache.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/lib/device/bcache.c b/lib/device/bcache.c +index 4c91402c4..38da044b1 100644 +--- a/lib/device/bcache.c ++++ b/lib/device/bcache.c +@@ -17,6 +17,7 @@ + #include "base/data-struct/radix-tree.h" + #include "lib/log/lvm-logging.h" + #include "lib/log/log.h" ++#include "lib/misc/lvm-signal.h" + + #include + #include +@@ -331,9 +332,13 @@ static bool _async_wait(struct io_engine *ioe, io_complete_fn fn) + + memset(&event, 0, sizeof(event)); + ++ /* ++ * Retry on EINTR from stray signals, but stop if an LVM interrupt ++ * signal (SIGINT/SIGTERM via sigint_allow()) has been caught. ++ */ + do { + r = io_getevents(e->aio_context, 1, MAX_EVENT, event, NULL); +- } while (r == -EINTR); ++ } while (r == -EINTR && !sigint_caught()); + + if (r < 0) { + log_sys_warn("io_getevents", -r); +-- +2.54.0 + diff --git a/0147-bcache-fix-_wait_io-failure-ignored-by-_wait_all-and.patch b/0147-bcache-fix-_wait_io-failure-ignored-by-_wait_all-and.patch new file mode 100644 index 0000000..e95c6d8 --- /dev/null +++ b/0147-bcache-fix-_wait_io-failure-ignored-by-_wait_all-and.patch @@ -0,0 +1,136 @@ +From 6e47af32fc2eb4d7d4a110c213b7eba308be0985 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 24 Feb 2026 00:47:20 +0100 +Subject: [PATCH 147/211] bcache: fix _wait_io failure ignored by _wait_all and + _wait_specific + +1. Use stack backtrace for EINTR - sigint_caught() already emits + log_error("Interrupted..."), so only a backtrace marker is needed + here; the previous log_sys_warn was incorrect since io_getevents + returns -errno directly. + +2. Break _wait_all/_wait_specific loops when _wait_io fails - + previously the return value was discarded causing infinite + busy-spin since BF_IO_PENDING is only cleared by _complete_io + which is never called when io_getevents returns EINTR. + +3. Propagate _wait_specific failure through _lookup_or_read_block + returning NULL so bcache_get and callers can detect the + interrupted I/O and abort the operation. + +4. Make _wait_all() return bool and update all three callers to check + the return value: + + - _new_block: return NULL on failure (same path as I/O errors) + - bcache_flush: return false immediately without checking errored list + (writes never completed so the list state is unreliable) + - bcache_invalidate_di: return false on failure + +Co-Authored-By: Claude Sonnet 4.6 + +diff --git a/lib/device/bcache.c b/lib/device/bcache.c +index 770d7d8ff..4dc5f9a2f 100644 +--- a/lib/device/bcache.c ++++ b/lib/device/bcache.c + +(cherry picked from commit 4e82d987df02a0f2e71b56519196ab4b5ad7403b) +--- + lib/device/bcache.c | 34 ++++++++++++++++++++++++---------- + 1 file changed, 24 insertions(+), 10 deletions(-) + +diff --git a/lib/device/bcache.c b/lib/device/bcache.c +index 38da044b1..bf8d873f6 100644 +--- a/lib/device/bcache.c ++++ b/lib/device/bcache.c +@@ -341,7 +341,10 @@ static bool _async_wait(struct io_engine *ioe, io_complete_fn fn) + } while (r == -EINTR && !sigint_caught()); + + if (r < 0) { +- log_sys_warn("io_getevents", -r); ++ if (r == -EINTR) ++ stack; ++ else ++ log_sys_warn("io_getevents", -r); + return false; + } + +@@ -922,16 +925,20 @@ static bool _wait_io(struct bcache *cache) + * High level IO handling + *--------------------------------------------------------------*/ + +-static void _wait_all(struct bcache *cache) ++static bool _wait_all(struct bcache *cache) + { + while (!dm_list_empty(&cache->io_pending)) +- _wait_io(cache); ++ if (!_wait_io(cache)) ++ return false; ++ return true; + } + +-static void _wait_specific(struct block *b) ++static bool _wait_specific(struct block *b) + { + while (_test_flags(b, BF_IO_PENDING)) +- _wait_io(b->cache); ++ if (!_wait_io(b->cache)) ++ return false; ++ return true; + } + + static unsigned _writeback(struct bcache *cache, unsigned count) +@@ -983,7 +990,8 @@ static struct block *_new_block(struct bcache *cache, int di, block_address i, b + if (can_wait) { + if (dm_list_empty(&cache->io_pending)) + _writeback(cache, 16); // FIXME: magic number +- _wait_all(cache); ++ if (!_wait_all(cache)) ++ return NULL; + if (dm_list_size(&cache->errored) >= cache->max_io) { + log_debug("bcache no new blocks for di %d index %u with >%d errors.", + di, (uint32_t) i, cache->max_io); +@@ -1061,7 +1069,8 @@ static struct block *_lookup_or_read_block(struct bcache *cache, + + if (_test_flags(b, BF_IO_PENDING)) { + _miss(cache, flags); +- _wait_specific(b); ++ if (!_wait_specific(b)) ++ return NULL; + + } else + _hit(b, flags); +@@ -1081,7 +1090,10 @@ static struct block *_lookup_or_read_block(struct bcache *cache, + + else { + _issue_read(b); +- _wait_specific(b); ++ if (!_wait_specific(b)) { ++ _unlink_block(b); ++ return NULL; ++ } + + // we know the block is clean and unerrored. + _unlink_block(b); +@@ -1325,7 +1337,8 @@ bool bcache_flush(struct bcache *cache) + */ + _writeback(cache, cache->nr_cache_blocks); + +- _wait_all(cache); ++ if (!_wait_all(cache)) ++ return false; + + return dm_list_empty(&cache->errored); + } +@@ -1422,7 +1435,8 @@ bool bcache_invalidate_di(struct bcache *cache, int di) + it.it.visit = _writeback_v; + radix_tree_iterate(cache->rtree, k.bytes, sizeof(k.parts.di), &it.it); + +- _wait_all(cache); ++ if (!_wait_all(cache)) ++ return false; + + it.success = true; + it.it.visit = _invalidate_v; +-- +2.54.0 + diff --git a/0148-libdm-fix-cache-feature_flags-cleaner-policy-to-pres.patch b/0148-libdm-fix-cache-feature_flags-cleaner-policy-to-pres.patch new file mode 100644 index 0000000..ccce61e --- /dev/null +++ b/0148-libdm-fix-cache-feature_flags-cleaner-policy-to-pres.patch @@ -0,0 +1,32 @@ +From 8239bbf3cad6fba2db70bcf62ff24eb275ded214 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 10 Feb 2026 13:11:24 +0100 +Subject: [PATCH 148/211] libdm: fix cache feature_flags cleaner policy to + preserve non-mode bits + +When enforcing writethrough mode for cleaner policy, the code was +assigning ~modemask to feature_flags instead of masking with &=. +This discarded non-mode feature flags like DM_CACHE_FEATURE_METADATA2. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 8b955d13ea0d4eeb14427c68ff2939679766bbfa) +--- + libdm/libdm-deptree.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/libdm-deptree.c b/libdm/libdm-deptree.c +index 318b23ecc..cfb57854d 100644 +--- a/libdm/libdm-deptree.c ++++ b/libdm/libdm-deptree.c +@@ -3442,7 +3442,7 @@ DM_EXPORT_NEW_SYMBOL(int, dm_tree_node_add_cache_target, 1_02_138) + case DM_CACHE_FEATURE_WRITEBACK: + if (strcmp(policy_name, "cleaner") == 0) { + /* Enforce writethrough mode for cleaner policy */ +- feature_flags = ~_modemask; ++ feature_flags &= ~_modemask; + feature_flags |= DM_CACHE_FEATURE_WRITETHROUGH; + } + /* Fall through */ +-- +2.54.0 + diff --git a/0149-libdm-fix-cache-origin-uuid-error-message-printing-w.patch b/0149-libdm-fix-cache-origin-uuid-error-message-printing-w.patch new file mode 100644 index 0000000..1e0d287 --- /dev/null +++ b/0149-libdm-fix-cache-origin-uuid-error-message-printing-w.patch @@ -0,0 +1,31 @@ +From aff798f2816e323163da9878031ba8f3f0385a26 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 10 Feb 2026 13:11:43 +0100 +Subject: [PATCH 149/211] libdm: fix cache origin uuid error message printing + wrong variable + +The error message for missing cache origin was printing metadata_uuid +instead of origin_uuid. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 045f941798f06f88121aac44a4e9a19f97de1a30) +--- + libdm/libdm-deptree.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/libdm-deptree.c b/libdm/libdm-deptree.c +index cfb57854d..2fdd9fa07 100644 +--- a/libdm/libdm-deptree.c ++++ b/libdm/libdm-deptree.c +@@ -3490,7 +3490,7 @@ DM_EXPORT_NEW_SYMBOL(int, dm_tree_node_add_cache_target, 1_02_138) + if (!(seg->origin = dm_tree_find_node_by_uuid(node->dtree, + origin_uuid))) { + log_error("Missing cache's origin uuid %s.", +- metadata_uuid); ++ origin_uuid); + return 0; + } + if (!_link_tree_nodes(node, seg->origin)) +-- +2.54.0 + diff --git a/0150-lvmlockd-check-if-sanlock-lv-is-not-partial-before-t.patch b/0150-lvmlockd-check-if-sanlock-lv-is-not-partial-before-t.patch new file mode 100644 index 0000000..228c2ae --- /dev/null +++ b/0150-lvmlockd-check-if-sanlock-lv-is-not-partial-before-t.patch @@ -0,0 +1,40 @@ +From 338c45f3128680ca511c4c92402999bcbe8df6b1 Mon Sep 17 00:00:00 2001 +From: Peter Rajnoha +Date: Tue, 10 Feb 2026 11:21:51 +0100 +Subject: [PATCH 150/211] lvmlockd: check if sanlock lv is not partial before + trying to activate + +If the sanlock lv is partial, we can't execute lockd operations, like 'lock start'. +The activate_lv would check if the lv is partial or not, but it would provide +a misleading hint in this case: + + "Refusing activation of partial LV . Use '--activationmode partial' to override." + +Instead, check for partial sanlock lv early and provide this message instead: + + "Cannot use sanlock lv / with missing PVs." + +(cherry picked from commit 365b9768dfc542689424a1840a208229cf43b25d) +--- + lib/locking/lvmlockd.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/lib/locking/lvmlockd.c b/lib/locking/lvmlockd.c +index 68dea52e0..af827e49e 100644 +--- a/lib/locking/lvmlockd.c ++++ b/lib/locking/lvmlockd.c +@@ -782,6 +782,11 @@ static int _handle_sanlock_lv(struct cmd_context *cmd, struct volume_group *vg) + + static int _activate_sanlock_lv(struct cmd_context *cmd, struct volume_group *vg) + { ++ if (lv_is_partial(vg->sanlock_lv)) { ++ log_error("Cannot use sanlock lv %s/%s with missing PVs.", vg->name, vg->sanlock_lv->name); ++ return 0; ++ } ++ + if (!activate_lv(cmd, vg->sanlock_lv)) { + log_error("Failed to activate sanlock lv %s/%s", vg->name, vg->sanlock_lv->name); + return 0; +-- +2.54.0 + diff --git a/0151-pool-initialize-chunk_size_calc_method-in-update-fun.patch b/0151-pool-initialize-chunk_size_calc_method-in-update-fun.patch new file mode 100644 index 0000000..bf02010 --- /dev/null +++ b/0151-pool-initialize-chunk_size_calc_method-in-update-fun.patch @@ -0,0 +1,57 @@ +From 83676b371fd99ca2c4ab9bc8932661539eec90b9 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 23 Oct 2025 19:18:16 +0200 +Subject: [PATCH 151/211] pool: initialize chunk_size_calc_method in update + functions + +Fix uninitialized variable bug causing valgrind warnings when +converting LVs to pools with user-specified --chunksize. + +The update_thin_pool_params() and update_cache_pool_params() +functions only initialized chunk_size_calc_method in the code +path that calculates default chunk size from profile settings. +When users specified --chunksize, this output parameter remained +uninitialized and was later used in a conditional check in +recalculate_pool_chunk_size_with_dev_hints(), triggering valgrind: +"Conditional jump or move depends on uninitialised value(s)" + +Fix by initializing chunk_size_calc_method to 0 at the start of +both functions. A value of 0 indicates chunk size was user-specified +or should not be recalculated from device hints. + +Co-Authored-By: Claude +(cherry picked from commit 620cc808d1a47e007f40d0666f10048577eaa4d7) +--- + lib/metadata/cache_manip.c | 2 ++ + lib/metadata/thin_manip.c | 2 ++ + 2 files changed, 4 insertions(+) + +diff --git a/lib/metadata/cache_manip.c b/lib/metadata/cache_manip.c +index 3e7b34a66..fecf56919 100644 +--- a/lib/metadata/cache_manip.c ++++ b/lib/metadata/cache_manip.c +@@ -212,6 +212,8 @@ int update_cache_pool_params(struct cmd_context *cmd, + DM_CACHE_MIN_DATA_BLOCK_SIZE - 1) / + DM_CACHE_MIN_DATA_BLOCK_SIZE) * DM_CACHE_MIN_DATA_BLOCK_SIZE; + ++ *chunk_size_calc_method = 0; ++ + if (!*chunk_size) { + if (!(*chunk_size = find_config_tree_int(cmd, allocation_cache_pool_chunk_size_CFG, + profile) * 2)) { +diff --git a/lib/metadata/thin_manip.c b/lib/metadata/thin_manip.c +index b741ba0f5..b9db5d4b0 100644 +--- a/lib/metadata/thin_manip.c ++++ b/lib/metadata/thin_manip.c +@@ -832,6 +832,8 @@ int update_thin_pool_params(struct cmd_context *cmd, + uint64_t max_pool_data_size; + const char *str; + ++ *chunk_size_calc_method = 0; ++ + if (!*chunk_size && + find_config_tree_node(cmd, allocation_thin_pool_chunk_size_CFG, profile)) + *chunk_size = find_config_tree_int(cmd, allocation_thin_pool_chunk_size_CFG, profile) * 2; +-- +2.54.0 + diff --git a/0152-pool-drop-LV_ACTIVATION_SKIP-from-converted-LVs.patch b/0152-pool-drop-LV_ACTIVATION_SKIP-from-converted-LVs.patch new file mode 100644 index 0000000..3b68762 --- /dev/null +++ b/0152-pool-drop-LV_ACTIVATION_SKIP-from-converted-LVs.patch @@ -0,0 +1,59 @@ +From 23e65201eee7460e6a40ddce520271924229d46c Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 14 Dec 2025 15:11:00 +0100 +Subject: [PATCH 152/211] pool: drop LV_ACTIVATION_SKIP from converted LVs + +When converting LVs to use them as pool data & metadata LVs, +drop LV_ACTIVATION_SKIP from their status. + +This flag is not meaningful for internal LVs, and for thin-pools +it has a side-effect selecting whether the thin-pool should be +left active standalone. + +(cherry picked from commit 0d0b42e418aac36c88aecb19aabc0b8d7b81cab3) +--- + lib/metadata/pool_manip.c | 3 +++ + tools/lvconvert.c | 6 ++++++ + 2 files changed, 9 insertions(+) + +diff --git a/lib/metadata/pool_manip.c b/lib/metadata/pool_manip.c +index cbe1b3ff0..eb081d978 100644 +--- a/lib/metadata/pool_manip.c ++++ b/lib/metadata/pool_manip.c +@@ -38,6 +38,7 @@ int attach_pool_metadata_lv(struct lv_segment *pool_seg, + return 0; + } + pool_seg->metadata_lv = metadata_lv; ++ metadata_lv->status &= ~LV_ACTIVATION_SKIP; /* Internal volume does not use skip */ + metadata_lv->status |= seg_is_thin_pool(pool_seg) ? + THIN_POOL_METADATA : CACHE_POOL_METADATA; + lv_set_hidden(metadata_lv); +@@ -80,6 +81,8 @@ int attach_pool_data_lv(struct lv_segment *pool_seg, + THIN_POOL_DATA : CACHE_POOL_DATA)) + return_0; + ++ pool_data_lv->status &= ~LV_ACTIVATION_SKIP; ++ pool_seg->lv->status &= ~LV_ACTIVATION_SKIP; + pool_seg->lv->status |= seg_is_thin_pool(pool_seg) ? + THIN_POOL : CACHE_POOL; + lv_set_hidden(pool_data_lv); +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index 7a17028e5..7dc8a3736 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -3441,6 +3441,12 @@ static int _lvconvert_to_pool(struct cmd_context *cmd, + pool_lv = lv; + } + ++ /* For pool skipping activation has slightly different meaning ++ * so by default we create a regular pool with standard activation ++ * User may use 'lvchange --setactivationskip' to use pool ++ * with skipped activation */ ++ pool_lv->status &= ~LV_ACTIVATION_SKIP; ++ + /* + * starts with pool_lv foo (not a pool yet) + * creates new data_lv foo_tdata +-- +2.54.0 + diff --git a/0153-lib-fix-buffer-overflows-in-device_id-and-GPT-parsin.patch b/0153-lib-fix-buffer-overflows-in-device_id-and-GPT-parsin.patch new file mode 100644 index 0000000..a66eb30 --- /dev/null +++ b/0153-lib-fix-buffer-overflows-in-device_id-and-GPT-parsin.patch @@ -0,0 +1,66 @@ +From 80d6b94863e6f49f38e16090ca2067c8f640c0cc Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 16 Apr 2026 11:56:00 +0200 +Subject: [PATCH 153/211] lib: fix buffer overflows in device_id and GPT + parsing + +device_id: use dm_snprintf in device_ids_write to handle +truncation safely. + +dev-type: validate GPT partition entry size before iterating. +A crafted disk with sz_entry=0 causes ~4 billion loop iterations. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 8f39af43dd8d78ad99c1801cb0a2f0d1f5d2a19d) +--- + lib/device/dev-type.c | 6 ++++++ + lib/device/device_id.c | 13 ++++++------- + 2 files changed, 12 insertions(+), 7 deletions(-) + +diff --git a/lib/device/dev-type.c b/lib/device/dev-type.c +index 4c1040c9f..5c6717f01 100644 +--- a/lib/device/dev-type.c ++++ b/lib/device/dev-type.c +@@ -612,6 +612,12 @@ static int _has_gpt_partition_table(struct device *dev) + nr_entries = le32_to_cpu(gpt_header.nr_part_entries); + sz_entry = le32_to_cpu(gpt_header.sz_part_entry); + ++ if (sz_entry < sizeof(gpt_part_entry)) { ++ log_debug("GPT partition entry size %u too small on %s.", ++ sz_entry, dev_name(dev)); ++ return 0; ++ } ++ + for (i = 0; i < nr_entries; i++) { + if (!dev_read_bytes(dev, entries_start + (uint64_t)i * sz_entry, + sizeof(gpt_part_entry), &gpt_part_entry)) +diff --git a/lib/device/device_id.c b/lib/device/device_id.c +index a83257e50..73306558b 100644 +--- a/lib/device/device_id.c ++++ b/lib/device/device_id.c +@@ -1830,16 +1830,15 @@ int device_ids_write(struct cmd_context *cmd) + + t = time(NULL); + +- if ((fc_bytes = snprintf(fc, sizeof(fc), +- "# LVM uses devices listed in this file.\n" \ +- "# Created by LVM command %s%s pid %d at %s" \ +- "# HASH=%u\n", +- cmd->name, cmd->device_ids_auto_import ? " (auto)" : "", +- getpid(), ctime(&t), hash)) < 0) { ++ if ((fc_bytes = dm_snprintf(fc, sizeof(fc), ++ "# LVM uses devices listed in this file.\n" ++ "# Created by LVM command %s%s pid %d at %s" ++ "# HASH=%u\n", ++ cmd->name, cmd->device_ids_auto_import ? " (auto)" : "", ++ getpid(), ctime(&t), hash)) < 0) { + log_warn("Failed to write buffer for devices file content."); + goto out; + } +- fc[fc_bytes] = '\0'; + + if (fputs(fc, fp) < 0) { + log_warn("Failed to write devices file header."); +-- +2.54.0 + diff --git a/0154-lib-add-missing-NULL-checks-to-prevent-dereference-c.patch b/0154-lib-add-missing-NULL-checks-to-prevent-dereference-c.patch new file mode 100644 index 0000000..e184854 --- /dev/null +++ b/0154-lib-add-missing-NULL-checks-to-prevent-dereference-c.patch @@ -0,0 +1,64 @@ +From 483caa0b663c8b1b180967856f495bae5fe978a2 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 16 Apr 2026 11:55:40 +0200 +Subject: [PATCH 154/211] lib: add missing NULL checks to prevent dereference + crashes + +cache_manip: check cn->v before dereferencing in cache_set_policy +and cache_vol_set_params - a config section node has NULL value. + +label: check scan_bcache in dev_invalidate_bytes and dev_invalidate, +matching the guard already present in peer functions. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 36fe3bfb26dced3bd33018f4d820dd895f5a4074) +--- + lib/label/label.c | 4 ++++ + lib/metadata/cache_manip.c | 4 ++-- + 2 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/lib/label/label.c b/lib/label/label.c +index 12a825c62..d114c8e42 100644 +--- a/lib/label/label.c ++++ b/lib/label/label.c +@@ -1940,11 +1940,15 @@ bool dev_write_bytes(struct device *dev, uint64_t start, size_t len, void *data) + + bool dev_invalidate_bytes(struct device *dev, uint64_t start, size_t len) + { ++ if (!scan_bcache) ++ return true; + return bcache_invalidate_bytes(scan_bcache, dev->bcache_di, start, len); + } + + void dev_invalidate(struct device *dev) + { ++ if (!scan_bcache) ++ return; + bcache_invalidate_di(scan_bcache, dev->bcache_di); + } + +diff --git a/lib/metadata/cache_manip.c b/lib/metadata/cache_manip.c +index fecf56919..7bf0a2255 100644 +--- a/lib/metadata/cache_manip.c ++++ b/lib/metadata/cache_manip.c +@@ -858,7 +858,7 @@ int cache_set_policy(struct lv_segment *lvseg, const char *name, + restart: /* remove any 'default" nodes */ + cn = seg->policy_settings ? seg->policy_settings->child : NULL; + while (cn) { +- if (cn->v->type == DM_CFG_STRING && !strcmp(cn->v->v.str, "default")) { ++ if (cn->v && cn->v->type == DM_CFG_STRING && !strcmp(cn->v->v.str, "default")) { + dm_config_remove_node(seg->policy_settings, cn); + goto restart; + } +@@ -1157,7 +1157,7 @@ int cache_vol_set_params(struct cmd_context *cmd, + restart: /* remove any 'default" nodes */ + cn = policy_settings ? policy_settings->child : NULL; + while (cn) { +- if (cn->v->type == DM_CFG_STRING && !strcmp(cn->v->v.str, "default")) { ++ if (cn->v && cn->v->type == DM_CFG_STRING && !strcmp(cn->v->v.str, "default")) { + dm_config_remove_node(policy_settings, cn); + goto restart; + } +-- +2.54.0 + diff --git a/0155-pvck-fix-buffer-overflow-integer-truncation-and-type.patch b/0155-pvck-fix-buffer-overflow-integer-truncation-and-type.patch new file mode 100644 index 0000000..483104f --- /dev/null +++ b/0155-pvck-fix-buffer-overflow-integer-truncation-and-type.patch @@ -0,0 +1,85 @@ +From d075514407ffda578ac763491857f2abcab56667 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 16 Apr 2026 01:59:10 +0200 +Subject: [PATCH 155/211] pvck: fix buffer overflow, integer truncation, and + type mismatches + +- _chars_to_hexstr: memcpy used hardcoded 256 instead of max parameter +- _backup_file_to_raw_metadata: back_size * 2 truncated uint64_t to uint32_t +- _dump_backup_to_raw, _read_metadata_file: read() rv was int, compared + via (int) cast against uint64_t sizes losing high bits + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 60fa392c72d7aa299a1488506608c619cac3a362) +--- + tools/pvck.c | 19 +++++++++++++------ + 1 file changed, 13 insertions(+), 6 deletions(-) + +diff --git a/tools/pvck.c b/tools/pvck.c +index 3be469562..325590f11 100644 +--- a/tools/pvck.c ++++ b/tools/pvck.c +@@ -123,7 +123,7 @@ static char *_chars_to_hexstr(const void *in, void *out, int num, int max, const + i++; + } + +- memcpy(out, tmp, 256); ++ memcpy(out, tmp, max); + + free(tmp); + +@@ -2696,7 +2696,12 @@ static int _backup_file_to_raw_metadata(char *back_buf, uint64_t back_size, + uint32_t text_pos, pre_len = 0, back_pos, text_max; + int len, len2, vgnamelen; + +- text_max = back_size * 2; ++ if (back_size > UINT32_MAX / 2) { ++ log_error("Backup file too large."); ++ return 0; ++ } ++ ++ text_max = (uint32_t)(back_size * 2); + + if (!(text_buf = zalloc(text_max))) + return_0; +@@ -2799,7 +2804,8 @@ static int _dump_backup_to_raw(struct cmd_context *cmd, struct settings *set) + struct stat sb; + char *back_buf, *text_buf; + uint64_t back_size, text_size; +- int fd, rv, ret; ++ ssize_t rv; ++ int fd, ret; + + if (arg_is_set(cmd, file_ARG)) { + if (!(tofile = arg_str_value(cmd, file_ARG, NULL))) +@@ -2830,7 +2836,7 @@ static int _dump_backup_to_raw(struct cmd_context *cmd, struct settings *set) + goto fail_close; + + rv = read(fd, back_buf, back_size); +- if (rv != (int)back_size) { ++ if (rv != (ssize_t)back_size) { + log_error("Cannot read file: %s", input); + free(back_buf); + goto fail_close; +@@ -2946,7 +2952,8 @@ static int _read_metadata_file(struct cmd_context *cmd, struct metadata_file *mf + char *text_buf; + uint64_t text_size; + uint32_t text_crc; +- int fd, rv; ++ ssize_t rv; ++ int fd; + + if ((fd = open(mf->filename, O_RDONLY)) < 0) { + log_error("Cannot open file: %s", mf->filename); +@@ -2967,7 +2974,7 @@ static int _read_metadata_file(struct cmd_context *cmd, struct metadata_file *mf + goto_out; + + rv = read(fd, text_buf, text_size); +- if (rv != (int)text_size) { ++ if (rv != (ssize_t)text_size) { + log_error("Cannot read file: %s", mf->filename); + free(text_buf); + goto out; +-- +2.54.0 + diff --git a/0156-lvmpolld-replace-asserts-with-proper-error-handling.patch b/0156-lvmpolld-replace-asserts-with-proper-error-handling.patch new file mode 100644 index 0000000..853946a --- /dev/null +++ b/0156-lvmpolld-replace-asserts-with-proper-error-handling.patch @@ -0,0 +1,74 @@ +From 247ca0605f7d0602666abe0cb3d45fb02ebac4a7 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 16 Apr 2026 00:52:57 +0200 +Subject: [PATCH 156/211] lvmpolld: replace asserts with proper error handling + +Replace assert(read_single_line()) with if-continue - assert with +side effects compiles out with NDEBUG, silently skipping pipe reads +and leaving data->line stale for subsequent log calls. + +Replace assert(type < POLL_TYPE_MAX) with proper bounds check +returning EINVAL - assert vanishes with NDEBUG allowing +out-of-bounds array access. + +Check devicesfile strdup failure in pdlv_create() - the allocation +was not validated unlike all other fields in the struct. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit d0c1a22ce823e54eec42e6d2888b20dbcb8306a2) +--- + daemons/lvmpolld/lvmpolld-core.c | 9 ++++++--- + daemons/lvmpolld/lvmpolld-data-utils.c | 3 ++- + 2 files changed, 8 insertions(+), 4 deletions(-) + +diff --git a/daemons/lvmpolld/lvmpolld-core.c b/daemons/lvmpolld/lvmpolld-core.c +index 65ac18ab5..8ede78597 100644 +--- a/daemons/lvmpolld/lvmpolld-core.c ++++ b/daemons/lvmpolld/lvmpolld-core.c +@@ -269,7 +269,8 @@ static int poll_for_output(struct lvmpolld_lv *pdlv, struct lvmpolld_thread_data + if (fds[0].revents & POLLIN) { + DEBUGLOG(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "caught input data in STDOUT"); + +- assert(read_single_line(data, 0)); /* may block indef. anyway */ ++ if (!read_single_line(data, 0)) ++ continue; /* may block indef. anyway */ + INFO(pdlv->ls, "%s: PID %d: %s: '%s'", LVM2_LOG_PREFIX, + pdlv->cmd_pid, "STDOUT", data->line); + } else if (fds[0].revents) { +@@ -287,7 +288,8 @@ static int poll_for_output(struct lvmpolld_lv *pdlv, struct lvmpolld_thread_data + DEBUGLOG(pdlv->ls, "%s: %s", PD_LOG_PREFIX, + "caught input data in STDERR"); + +- assert(read_single_line(data, 1)); /* may block indef. anyway */ ++ if (!read_single_line(data, 1)) ++ continue; /* may block indef. anyway */ + WARN(pdlv->ls, "%s: PID %d: %s: '%s'", LVM2_LOG_PREFIX, + pdlv->cmd_pid, "STDERR", data->line); + } else if (fds[1].revents) { +@@ -628,7 +630,8 @@ static response poll_init(client_handle h, struct lvmpolld_state *ls, request re + const char *devicesfile = daemon_request_str(req, LVMPD_PARM_DEVICESFILE, NULL); + unsigned abort_polling = daemon_request_int(req, LVMPD_PARM_ABORT, 0); + +- assert(type < POLL_TYPE_MAX); ++ if (type >= POLL_TYPE_MAX) ++ return reply(LVMPD_RESP_EINVAL, REASON_ILLEGAL_ABORT_REQUEST); + + if (abort_polling && type != PVMOVE) + return reply(LVMPD_RESP_EINVAL, REASON_ILLEGAL_ABORT_REQUEST); +diff --git a/daemons/lvmpolld/lvmpolld-data-utils.c b/daemons/lvmpolld/lvmpolld-data-utils.c +index 8a4c6f8b0..111a0f2b9 100644 +--- a/daemons/lvmpolld/lvmpolld-data-utils.c ++++ b/daemons/lvmpolld/lvmpolld-data-utils.c +@@ -121,7 +121,8 @@ struct lvmpolld_lv *pdlv_create(struct lvmpolld_state *ls, const char *id, + .init_rq_count = 1 + }, *pdlv = (struct lvmpolld_lv *) malloc(sizeof(struct lvmpolld_lv)); + +- if (!pdlv || !tmp.lvmpolld_id || !tmp.lvname || !tmp.lvm_system_dir_env || !tmp.sinterval) ++ if (!pdlv || !tmp.lvmpolld_id || !tmp.lvname || !tmp.lvm_system_dir_env || !tmp.sinterval || ++ (devicesfile && !tmp.devicesfile)) + goto err; + + tmp.lvid = _get_lvid(tmp.lvmpolld_id, sysdir); +-- +2.54.0 + diff --git a/0157-import_vsn1-validate-area_count-before-subtracting-p.patch b/0157-import_vsn1-validate-area_count-before-subtracting-p.patch new file mode 100644 index 0000000..9d9f893 --- /dev/null +++ b/0157-import_vsn1-validate-area_count-before-subtracting-p.patch @@ -0,0 +1,46 @@ +From 77967846b66b69eacca94a88c092d31515648410 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 10 Apr 2026 10:52:31 +0200 +Subject: [PATCH 157/211] import_vsn1: validate area_count before subtracting + parity_devs + +The RAID extent calculation subtracts parity_devs from area_count +without checking that area_count is large enough: + + area_count - segtype->parity_devs + +Both are uint32_t. If an attacker crafts RAID6 metadata with +device_count = 1, the subtraction underflows (1 - 2 = 0xFFFFFFFF), +producing an invalid stripes value passed to raid_rimage_extents(). + +Add validation that area_count > parity_devs before the subtraction +to reject semantically invalid metadata (fewer devices than parity +requires). + +Reported-by: Tony Asleson +Co-Authored-By: Claude Sonnet 4.5 +(cherry picked from commit 73d24094985d7e27dc6e0f02f898747609b3635b) +--- + lib/format_text/import_vsn1.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/lib/format_text/import_vsn1.c b/lib/format_text/import_vsn1.c +index 20b1739e9..5b84d875f 100644 +--- a/lib/format_text/import_vsn1.c ++++ b/lib/format_text/import_vsn1.c +@@ -440,6 +440,12 @@ static int _read_segment(struct cmd_context *cmd, + !segtype->ops->text_import_area_count(sn_child, &area_count)) + return_0; + ++ if (segtype->parity_devs && area_count <= segtype->parity_devs) { ++ log_error("area_count %u must exceed parity_devs %u for segment in %s.", ++ area_count, segtype->parity_devs, display_lvname(lv)); ++ return 0; ++ } ++ + area_extents = segtype->parity_devs ? + raid_rimage_extents(segtype, extent_count, area_count - segtype->parity_devs, data_copies) : extent_count; + if (!(seg = alloc_lv_segment(segtype, lv, start_extent, +-- +2.54.0 + diff --git a/0158-format-text-validate-mda-size-and-rlocn-size-limits.patch b/0158-format-text-validate-mda-size-and-rlocn-size-limits.patch new file mode 100644 index 0000000..4c39154 --- /dev/null +++ b/0158-format-text-validate-mda-size-and-rlocn-size-limits.patch @@ -0,0 +1,75 @@ +From 8903bd4c1c5393a45dcf3273d947ce125bf1c672 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 10 Apr 2026 10:52:21 +0200 +Subject: [PATCH 158/211] format-text: validate mda size and rlocn size limits + +Two related issues in metadata area validation: + +1. Integer underflow in mdah->size validation: The rlocn bounds + checks subtract MDA_HEADER_SIZE (512) from mdah->size without + verifying mdah->size >= MDA_HEADER_SIZE. If mdah->size = 0, + the subtraction underflows to 0xFFFFFFFFFFFFFE00, bypassing + all bounds checks. + + Fix: Add minimum size validation in _raw_read_mda_header() to + reject mdah->size < MDA_HEADER_SIZE. + +2. Unsafe uint64_t to uint32_t truncation: rlocn->size is a 64-bit + value from disk but passed as uint32_t to text_read_metadata(). + If rlocn->size exceeds UINT32_MAX, the cast silently discards + high bits. + + Fix: Add rlocn->size > UINT32_MAX check in the existing bounds + validation. Since wrap < rlocn->size (because rlocn->offset < + mdah->size), this guarantees wrap also fits in uint32_t. + +Both issues affect _vg_read_raw_area() and read_metadata_location_summary(). + +Reported-by: Tony Asleson +Co-Authored-By: Claude Sonnet 4.5 +(cherry picked from commit d214bc8b158deab09c799c6e3a585332f633d983) +--- + lib/format_text/format-text.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/lib/format_text/format-text.c b/lib/format_text/format-text.c +index 3165312d7..39f1afcad 100644 +--- a/lib/format_text/format-text.c ++++ b/lib/format_text/format-text.c +@@ -234,6 +234,13 @@ static int _raw_read_mda_header(struct mda_header *mdah, struct device_area *dev + *bad_fields |= BAD_MDA_START; + } + ++ if (mdah->size < MDA_HEADER_SIZE) { ++ log_warn("WARNING: Metadata area size %llu too small in mda header on %s at %llu.", ++ (unsigned long long)mdah->size, ++ dev_name(dev_area->dev), (unsigned long long)dev_area->start); ++ *bad_fields |= BAD_MDA_HEADER; ++ } ++ + *bad_fields &= ~ignore_bad_fields; + + if (*bad_fields) +@@ -418,7 +425,8 @@ static struct volume_group *_vg_read_raw_area(struct cmd_context *cmd, + + /* Validate rlocn fields fit within mda bounds before uint32_t cast */ + if (rlocn->offset >= mdah->size || +- rlocn->size > mdah->size - MDA_HEADER_SIZE) { ++ rlocn->size > mdah->size - MDA_HEADER_SIZE || ++ rlocn->size > UINT32_MAX) { + log_error("Metadata location out of bounds (offset %llu size %llu mda %llu) on %s.", + (unsigned long long)rlocn->offset, + (unsigned long long)rlocn->size, +@@ -1515,7 +1523,8 @@ int read_metadata_location_summary(const struct format_type *fmt, + + /* Validate rlocn fields fit within mda bounds before uint32_t cast */ + if (rlocn->offset >= mdah->size || +- rlocn->size > mdah->size - MDA_HEADER_SIZE) { ++ rlocn->size > mdah->size - MDA_HEADER_SIZE || ++ rlocn->size > UINT32_MAX) { + log_warn("WARNING: Metadata location out of bounds (offset %llu size %llu mda %llu) on %s.", + (unsigned long long)rlocn->offset, + (unsigned long long)rlocn->size, +-- +2.54.0 + diff --git a/0159-lv_manip-validate-area_count-against-MAX_STRIPES.patch b/0159-lv_manip-validate-area_count-against-MAX_STRIPES.patch new file mode 100644 index 0000000..d9d4310 --- /dev/null +++ b/0159-lv_manip-validate-area_count-against-MAX_STRIPES.patch @@ -0,0 +1,72 @@ +From 56e7c266bc2b6667481b589e230870c5086fb8cb Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 9 Apr 2026 21:34:07 +0200 +Subject: [PATCH 159/211] lv_manip: validate area_count against MAX_STRIPES + +Add _validate_area_count helper to prevent integer overflow in +multiplication before allocating segment areas. + +Reported-by: Tony Asleson +Co-Authored-By: Claude Sonnet 4.5 +(cherry picked from commit 30c19ac8d2b86895332bef2c1892403c7ceab0c4) +--- + lib/metadata/lv_manip.c | 23 +++++++++++++++++++++-- + 1 file changed, 21 insertions(+), 2 deletions(-) + +diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c +index 0fce14f21..72ab89211 100644 +--- a/lib/metadata/lv_manip.c ++++ b/lib/metadata/lv_manip.c +@@ -1024,6 +1024,15 @@ static uint32_t _round_to_stripe_boundary(struct volume_group *vg, uint32_t exte + return new_extents; + } + ++static int _validate_area_count(uint32_t area_count) ++{ ++ if (area_count > MAX_STRIPES) { ++ log_error(INTERNAL_ERROR "area_count %u exceeds maximum %u.", area_count, MAX_STRIPES); ++ return_0; ++ } ++ return 1; ++} ++ + /* + * All lv_segments get created here. + */ +@@ -1044,13 +1053,18 @@ struct lv_segment *alloc_lv_segment(const struct segment_type *segtype, + { + struct lv_segment *seg; + struct dm_pool *mem = lv->vg->vgmem; +- uint32_t areas_sz = area_count * sizeof(*seg->areas); ++ uint32_t areas_sz; + + if (!segtype) { + log_error(INTERNAL_ERROR "alloc_lv_segment: Missing segtype."); + return NULL; + } + ++ if (!_validate_area_count(area_count)) ++ return_NULL; ++ ++ areas_sz = area_count * sizeof(*seg->areas); ++ + if (!(seg = dm_pool_zalloc(mem, sizeof(*seg)))) + return_NULL; + +@@ -1354,7 +1368,12 @@ int set_lv_segment_area_lv(struct lv_segment *seg, uint32_t area_num, + int add_lv_segment_areas(struct lv_segment *seg, uint32_t new_area_count) + { + struct lv_segment_area *newareas; +- uint32_t areas_sz = new_area_count * sizeof(*newareas); ++ uint32_t areas_sz; ++ ++ if (!_validate_area_count(new_area_count)) ++ return_0; ++ ++ areas_sz = new_area_count * sizeof(*newareas); + + if (!(newareas = dm_pool_zalloc(seg->lv->vg->vgmem, areas_sz))) { + log_error("Failed to allocate widened LV segment for %s.", +-- +2.54.0 + diff --git a/0160-lvmcache-fix-use-after-free-on-vgid-update-failure.patch b/0160-lvmcache-fix-use-after-free-on-vgid-update-failure.patch new file mode 100644 index 0000000..7184102 --- /dev/null +++ b/0160-lvmcache-fix-use-after-free-on-vgid-update-failure.patch @@ -0,0 +1,30 @@ +From af71204b76908cdc13ccfd36e8171a440f492df8 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 16 Apr 2026 11:56:10 +0200 +Subject: [PATCH 160/211] lvmcache: fix use-after-free on vgid update failure + +When _lvmcache_update_vgid fails after vginfo was already inserted +into _vgname_hash, the error path frees vginfo without removing it +from the hash, leaving a dangling pointer. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 58ab3ef97891ab334137e271e9bc5f46acf7265a) +--- + lib/cache/lvmcache.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lib/cache/lvmcache.c b/lib/cache/lvmcache.c +index 4c95db7db..2da7c81ce 100644 +--- a/lib/cache/lvmcache.c ++++ b/lib/cache/lvmcache.c +@@ -1876,6 +1876,7 @@ static int _lvmcache_update_vgname(struct cmd_context *cmd, + } + + if (!_lvmcache_update_vgid(NULL, vginfo, vgid)) { ++ dm_hash_remove(_vgname_hash, vgname); + free(vginfo->vgname); + free(vginfo); + return_0; +-- +2.54.0 + diff --git a/0161-config-fix-strtoll-error-checking.patch b/0161-config-fix-strtoll-error-checking.patch new file mode 100644 index 0000000..93a70ab --- /dev/null +++ b/0161-config-fix-strtoll-error-checking.patch @@ -0,0 +1,41 @@ +From 7b33f39ef2f674d9ffc7f01c3d22bb9baca7ac6f Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 9 Apr 2026 21:33:58 +0200 +Subject: [PATCH 161/211] config: fix strtoll error checking + +Add proper validation of strtoll conversion by checking endptr +to detect incomplete conversions and trailing characters. + +Reported-by: Tony Asleson +Co-Authored-By: Claude Sonnet 4.5 +(cherry picked from commit 591a1c9310506124685cacc75aa338e31a148a06) +--- + libdm/libdm-config.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/libdm/libdm-config.c b/libdm/libdm-config.c +index 25cfa268c..3ecf29816 100644 +--- a/libdm/libdm-config.c ++++ b/libdm/libdm-config.c +@@ -725,6 +725,7 @@ static struct dm_config_value *_type(struct parser *p) + /* [+-]{0,1}[0-9]+ | [0-9]*\.[0-9]* | ".*" */ + struct dm_config_value *v; + const char *str; ++ char *endptr; + size_t len; + + switch (p->t) { +@@ -733,8 +734,8 @@ static struct dm_config_value *_type(struct parser *p) + break; + v->type = DM_CFG_INT; + errno = 0; +- v->v.i = strtoll(p->tb, NULL, 0); /* FIXME: check error */ +- if (errno) { ++ v->v.i = strtoll(p->tb, &endptr, 0); ++ if (errno || (endptr == p->tb) || (endptr != p->te)) { + if (errno == ERANGE && p->key && + strcmp("creation_time", p->key) == 0) { + /* Due to a bug in some older 32bit builds (<2.02.169), +-- +2.54.0 + diff --git a/0162-libdm-config-add-missing-NULL-check-for-n-v-in-_find.patch b/0162-libdm-config-add-missing-NULL-check-for-n-v-in-_find.patch new file mode 100644 index 0000000..8da6099 --- /dev/null +++ b/0162-libdm-config-add-missing-NULL-check-for-n-v-in-_find.patch @@ -0,0 +1,38 @@ +From d1bb06be39ef3d7862f0fbc1a2e1767d9b003079 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Mon, 20 Apr 2026 20:22:35 +0200 +Subject: [PATCH 162/211] libdm: config: add missing NULL check for n->v in + _find_config_bool + +All other _find_config_* lookup functions guard against n->v being +NULL before dereferencing (e.g. _find_config_int64 checks +"if (n && n->v && n->v->type == DM_CFG_INT)"), but _find_config_bool +only checked "if (n)" before accessing n->v->type. + +A config section node (parsed from "section { ... }") has n->v == NULL +since it carries children, not a value. Querying such a node via +dm_config_find_bool() or dm_config_tree_find_bool() would dereference +the NULL pointer and crash. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit e029c2d4cd65fcbcd909bbc3cd75f8aebe098dbd) +--- + libdm/libdm-config.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/libdm-config.c b/libdm/libdm-config.c +index 3ecf29816..25b206dbd 100644 +--- a/libdm/libdm-config.c ++++ b/libdm/libdm-config.c +@@ -1152,7 +1152,7 @@ static int _find_config_bool(const void *start, node_lookup_fn find, + const struct dm_config_value *v; + int b; + +- if (n) { ++ if (n && n->v) { + v = n->v; + + switch (v->type) { +-- +2.54.0 + diff --git a/0163-polldaemon-add-missing-NULL-check-for-display_name-w.patch b/0163-polldaemon-add-missing-NULL-check-for-display_name-w.patch new file mode 100644 index 0000000..6dcff9f --- /dev/null +++ b/0163-polldaemon-add-missing-NULL-check-for-display_name-w.patch @@ -0,0 +1,38 @@ +From 098fdeeb08e6573d83faafc2c2fd2d25623b9940 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 17:21:39 +0200 +Subject: [PATCH 163/211] polldaemon: add missing NULL check for display_name + when aborting + +_lvmpolld_poll_vg was missing the NULL check for id.display_name +that _poll_vg has at lines 351-355. When aborting with a NULL +display_name, the code would pass NULL to lvmpolld_poll_init. + +Add the same error check as the parallel _poll_vg function. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 19d8da0395f34b52ab3ac5f336cd4157be9722fe) +--- + tools/polldaemon.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/tools/polldaemon.c b/tools/polldaemon.c +index 605c90779..f5d04311e 100644 +--- a/tools/polldaemon.c ++++ b/tools/polldaemon.c +@@ -499,6 +499,12 @@ static int _lvmpolld_init_poll_vg(struct cmd_context *cmd, const char *vgname, + if (!id.display_name && !lpdp->parms->aborting) + continue; + ++ if (!id.display_name) { ++ log_error("Device name for LV %s not found in metadata.", ++ display_lvname(lv)); ++ return ECMD_FAILED; ++ } ++ + id.vg_name = lv->vg->name; + id.lv_name = lv->name; + +-- +2.54.0 + diff --git a/0164-toollib-fix-memory-leak-in-process_each_label-duplic.patch b/0164-toollib-fix-memory-leak-in-process_each_label-duplic.patch new file mode 100644 index 0000000..5bb36f8 --- /dev/null +++ b/0164-toollib-fix-memory-leak-in-process_each_label-duplic.patch @@ -0,0 +1,51 @@ +From 8302dcc16a39c477171ed7a56d13080db0b3c6e8 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 21 Apr 2026 13:50:49 +0200 +Subject: [PATCH 164/211] toollib: fix memory leak in process_each_label + duplicate handling + +process_each_label() used malloc() to allocate device_list entries +for the process_duplicates list. These allocations were never freed, +causing a memory leak when a duplicate PV device was reported. + +The leak is triggered when two block devices share the same PV UUID +and one is classified as an "unused duplicate" by lvmcache. For +example: + + pvcreate /dev/sda + dd if=/dev/sda of=/dev/sdb bs=4k count=2 + pvs -o name /dev/sda /dev/sdb + +The dd copies the PV label (including PVID) to the second device. +During label_scan, lvmcache picks one device as preferred and puts +the other in _unused_duplicates. When process_each_label() iterates +the argv list, it detects the unused duplicate and allocates a +device_list entry with malloc(), but never frees it. + +Replace malloc() with dm_pool_zalloc(cmd->mem, ...) to use the +command memory pool, consistent with all other device_list allocations +in the codebase (lvmcache.c, label.c). The pool is freed +automatically when the command finishes. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 63340783c81d1d453ea7ce419033c90f01fb22ed) +--- + tools/toollib.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/toollib.c b/tools/toollib.c +index b9fa67446..916aa6856 100644 +--- a/tools/toollib.c ++++ b/tools/toollib.c +@@ -1850,7 +1850,7 @@ int process_each_label(struct cmd_context *cmd, int argc, char **argv, + log_error("No physical volume label read from %s.", argv[opt]); + ret_max = ECMD_FAILED; + } else { +- if (!(devl = malloc(sizeof(*devl)))) ++ if (!(devl = dm_pool_zalloc(cmd->mem, sizeof(*devl)))) + return_0; + devl->dev = dev; + dm_list_add(&process_duplicates, &devl->list); +-- +2.54.0 + diff --git a/0165-lvmpolld-fix-pthread_attr-leak-in-spawn_detached_thr.patch b/0165-lvmpolld-fix-pthread_attr-leak-in-spawn_detached_thr.patch new file mode 100644 index 0000000..a3fa2bb --- /dev/null +++ b/0165-lvmpolld-fix-pthread_attr-leak-in-spawn_detached_thr.patch @@ -0,0 +1,47 @@ +From 88153f40e5d2ff1eda0c577e6ea7cc5595125c3c Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 20:32:27 +0200 +Subject: [PATCH 165/211] lvmpolld: fix pthread_attr leak in + spawn_detached_thread + +If pthread_attr_setdetachstate() fails, pthread_attr_destroy() was +never called, leaking the attribute object. + +Also, if pthread_create() succeeded but pthread_attr_destroy() failed, +the function returned failure, causing the caller to destroy the pdlv +that the already-running thread was using. + +Fix by always destroying the attr after use and not letting +pthread_attr_destroy() failure override the pthread_create() result. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 4b0cd2157261715d35ade6cf0f6033b71128c389) +--- + daemons/lvmpolld/lvmpolld-core.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/daemons/lvmpolld/lvmpolld-core.c b/daemons/lvmpolld/lvmpolld-core.c +index 8ede78597..3966754d1 100644 +--- a/daemons/lvmpolld/lvmpolld-core.c ++++ b/daemons/lvmpolld/lvmpolld-core.c +@@ -604,13 +604,14 @@ static int spawn_detached_thread(struct lvmpolld_lv *pdlv) + if (pthread_attr_init(&attr) != 0) + return 0; + +- if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) ++ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) { ++ pthread_attr_destroy(&attr); + return 0; ++ } + + r = pthread_create(&pdlv->tid, &attr, fork_and_poll, (void *)pdlv); + +- if (pthread_attr_destroy(&attr) != 0) +- return 0; ++ pthread_attr_destroy(&attr); + + return !r; + } +-- +2.54.0 + diff --git a/0166-vgimportclone-fix-VG-lock-leak-on-second-lock_vol-fa.patch b/0166-vgimportclone-fix-VG-lock-leak-on-second-lock_vol-fa.patch new file mode 100644 index 0000000..d678e06 --- /dev/null +++ b/0166-vgimportclone-fix-VG-lock-leak-on-second-lock_vol-fa.patch @@ -0,0 +1,30 @@ +From f91776679e0c0191ebfe198ee7d3fba806dc44ad Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 16:59:39 +0200 +Subject: [PATCH 166/211] vgimportclone: fix VG lock leak on second lock_vol + failure + +When locking old_vgname failed, the already-acquired lock on +new_vgname was not released before goto out. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 20d9142c3b9769d967a18d51b7a4787b89c2f855) +--- + tools/vgimportclone.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/tools/vgimportclone.c b/tools/vgimportclone.c +index f1852cda2..76c8e0d7f 100644 +--- a/tools/vgimportclone.c ++++ b/tools/vgimportclone.c +@@ -465,6 +465,7 @@ retry_name: + + if (!lock_vol(cmd, vp.old_vgname, LCK_VG_WRITE, NULL)) { + log_error("Can't get lock for VG name %s", vp.old_vgname); ++ unlock_vg(cmd, NULL, vp.new_vgname); + goto out; + } + +-- +2.54.0 + diff --git a/0167-pvscan-fix-VG-lock-leak-on-early-return-in-_pvscan_a.patch b/0167-pvscan-fix-VG-lock-leak-on-early-return-in-_pvscan_a.patch new file mode 100644 index 0000000..9d92778 --- /dev/null +++ b/0167-pvscan-fix-VG-lock-leak-on-early-return-in-_pvscan_a.patch @@ -0,0 +1,53 @@ +From b5af2c8f4cafa02fb347861ca2eacba39756afd7 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 16:59:21 +0200 +Subject: [PATCH 167/211] pvscan: fix VG lock leak on early return in + _pvscan_aa_quick + +After lock_vol succeeds, two error paths (vgid lookup failure +and vg_read failure) returned directly without unlocking. +Initialize vg to NULL and route through the existing out: +cleanup which calls unlock_vg and release_vg (both NULL-safe). + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 7d5c8697edba7c1f9c9597b5d693c4a09623bc54) +--- + tools/pvscan.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/tools/pvscan.c b/tools/pvscan.c +index 0539a2b48..3a5b55cb0 100644 +--- a/tools/pvscan.c ++++ b/tools/pvscan.c +@@ -663,7 +663,7 @@ static int _pvscan_aa_quick(struct cmd_context *cmd, struct pvscan_aa_params *pp + int *no_quick) + { + struct dm_list devs; /* device_list */ +- struct volume_group *vg; ++ struct volume_group *vg = NULL; + struct pv_list *pvl; + const char *vgid; + uint32_t lockd_state = 0; +@@ -712,7 +712,8 @@ static int _pvscan_aa_quick(struct cmd_context *cmd, struct pvscan_aa_params *pp + + if (!(vgid = lvmcache_vgid_from_vgname(cmd, vgname))) { + log_error_pvscan(cmd, "activation for VG %s failed to find vgid.", vgname); +- return ECMD_FAILED; ++ ret = ECMD_FAILED; ++ goto out; + } + + /* +@@ -732,7 +733,8 @@ static int _pvscan_aa_quick(struct cmd_context *cmd, struct pvscan_aa_params *pp + * cases that would be caught here. + */ + log_error_pvscan(cmd, "activation for VG %s cannot read (%x).", vgname, error_flags); +- return ECMD_FAILED; ++ ret = ECMD_FAILED; ++ goto out; + } + + /* +-- +2.54.0 + diff --git a/0168-cache-fix-cache_check_for_warns-reading-wrong-cache-.patch b/0168-cache-fix-cache_check_for_warns-reading-wrong-cache-.patch new file mode 100644 index 0000000..2903f71 --- /dev/null +++ b/0168-cache-fix-cache_check_for_warns-reading-wrong-cache-.patch @@ -0,0 +1,38 @@ +From b0923986c2a83db167169a01423dc87eb2b17f4f Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Mon, 20 Apr 2026 21:50:45 +0200 +Subject: [PATCH 168/211] cache: fix cache_check_for_warns reading wrong cache + mode for cachevol + +For cachevol-style caches, the cache mode is stored on the cache +segment itself (seg->cache_mode), not on first_seg(seg->pool_lv). +Every other function in cache_manip.c dispatches on +lv_is_cache_vol(seg->pool_lv) to read the correct location, but +cache_check_for_warns unconditionally read from the pool segment. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 6d2c7e7875c1da2e0ebb43cff24d116cbe4f292d) +--- + lib/metadata/cache_manip.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/lib/metadata/cache_manip.c b/lib/metadata/cache_manip.c +index 7bf0a2255..38c024c3b 100644 +--- a/lib/metadata/cache_manip.c ++++ b/lib/metadata/cache_manip.c +@@ -164,9 +164,10 @@ int cache_set_cache_mode(struct lv_segment *seg, cache_mode_t mode) + void cache_check_for_warns(const struct lv_segment *seg) + { + struct logical_volume *origin_lv = seg_lv(seg, 0); ++ cache_mode_t mode = lv_is_cache_vol(seg->pool_lv) ? ++ seg->cache_mode : first_seg(seg->pool_lv)->cache_mode; + +- if (lv_is_raid(origin_lv) && +- first_seg(seg->pool_lv)->cache_mode == CACHE_MODE_WRITEBACK) ++ if (lv_is_raid(origin_lv) && (mode == CACHE_MODE_WRITEBACK)) + log_warn("WARNING: Data redundancy could be lost with writeback " + "caching of raid logical volume!"); + } +-- +2.54.0 + diff --git a/0169-metadata-fix-vg_is_foreign-missing-NULL-and-empty-sy.patch b/0169-metadata-fix-vg_is_foreign-missing-NULL-and-empty-sy.patch new file mode 100644 index 0000000..d2c510c --- /dev/null +++ b/0169-metadata-fix-vg_is_foreign-missing-NULL-and-empty-sy.patch @@ -0,0 +1,36 @@ +From ce30b61208a5468bf165194cbf38615aea4b45fd Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Mon, 20 Apr 2026 20:50:03 +0200 +Subject: [PATCH 169/211] metadata: fix vg_is_foreign missing NULL and empty + system_id check + +vg_is_foreign() did not guard against NULL or empty vg->system_id, +inconsistent with is_system_id_allowed() which correctly handles both. + +A NULL system_id (after vg_set_system_id or vg_create) would crash +in strcmp. An empty system_id (VGs without system_id in metadata) +would incorrectly classify the VG as foreign. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 60fd17428512ec529d25691583f092c62d11ba98) +--- + lib/metadata/metadata.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/lib/metadata/metadata.c b/lib/metadata/metadata.c +index ab037fd06..ccc49659a 100644 +--- a/lib/metadata/metadata.c ++++ b/lib/metadata/metadata.c +@@ -4455,7 +4455,8 @@ int lv_on_pmem(struct logical_volume *lv) + + int vg_is_foreign(struct volume_group *vg) + { +- return vg->cmd->system_id && strcmp(vg->system_id, vg->cmd->system_id); ++ return vg->cmd->system_id && vg->system_id && vg->system_id[0] && ++ strcmp(vg->system_id, vg->cmd->system_id); + } + + void vg_write_commit_bad_mdas(struct cmd_context *cmd, struct volume_group *vg) +-- +2.54.0 + diff --git a/0170-lvchange-fix-swapped-printf-args-and-wrong-arg_long_.patch b/0170-lvchange-fix-swapped-printf-args-and-wrong-arg_long_.patch new file mode 100644 index 0000000..87add7c --- /dev/null +++ b/0170-lvchange-fix-swapped-printf-args-and-wrong-arg_long_.patch @@ -0,0 +1,63 @@ +From 84bff8f98ed84b1cec5cf05df73d0b761332d29b Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 16:41:27 +0200 +Subject: [PATCH 170/211] lvchange: fix swapped printf args and wrong + arg_long_option_name variable + +Fix two log_verbose calls where display_lvname(lv) and the +flag string were passed in wrong order, producing messages +like "Changing activation skip flag to vg/lv for LV enabled." + +Also fix two default cases in the properties loop that passed +loop index i instead of opt_enum to arg_long_option_name, +which would print the wrong option name in the error message. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit f1a67e95fa58fc7d046198f210854bf3fd011a9a) +--- + tools/lvchange.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/tools/lvchange.c b/tools/lvchange.c +index 0ccebd344..f2ed61fde 100644 +--- a/tools/lvchange.c ++++ b/tools/lvchange.c +@@ -1163,7 +1163,7 @@ static int _lvchange_activation_skip(struct logical_volume *lv, uint32_t *mr) + lv_set_activation_skip(lv, 1, skip); + + log_verbose("Changing activation skip flag to %s for LV %s.", +- display_lvname(lv), skip ? "enabled" : "disabled"); ++ skip ? "enabled" : "disabled", display_lvname(lv)); + + /* Request caller to commit+backup metadata */ + *mr |= MR_COMMIT; +@@ -1185,7 +1185,7 @@ static int _lvchange_autoactivation(struct logical_volume *lv, uint32_t *mr) + lv->status &= ~LV_NOAUTOACTIVATE; + + log_verbose("Changing autoactivation flag to %s for LV %s.", +- display_lvname(lv), aa_no_arg ? "no" : "yes"); ++ aa_no_arg ? "no" : "yes", display_lvname(lv)); + + /* Request caller to commit+backup metadata */ + *mr |= MR_COMMIT; +@@ -1457,7 +1457,7 @@ static int _lvchange_properties_single(struct cmd_context *cmd, + + default: + log_error(INTERNAL_ERROR "Failed to check for option %s", +- arg_long_option_name(i)); ++ arg_long_option_name(opt_enum)); + } + } + +@@ -1527,7 +1527,7 @@ static int _lvchange_properties_single(struct cmd_context *cmd, + break; + default: + log_error(INTERNAL_ERROR "Failed to check for option %s", +- arg_long_option_name(i)); ++ arg_long_option_name(opt_enum)); + } + + /* Display any logical volume change unless already displayed in step 1. */ +-- +2.54.0 + diff --git a/0171-pvchange-use-arg_force_value-instead-of-raw-arg_coun.patch b/0171-pvchange-use-arg_force_value-instead-of-raw-arg_coun.patch new file mode 100644 index 0000000..6534c12 --- /dev/null +++ b/0171-pvchange-use-arg_force_value-instead-of-raw-arg_coun.patch @@ -0,0 +1,34 @@ +From 8a268e429c450fd2c98c1f5cf252ecc1d5841ecf Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 16:20:46 +0200 +Subject: [PATCH 171/211] pvchange: use arg_force_value instead of raw + arg_count for -ff check + +arg_count returns the raw count of -f flags, so -fff yields 3, +which is != DONT_PROMPT_OVERRIDE (2), causing the check to fail +and reject -fff with an error about needing -ff. arg_force_value +clamps values above DONT_PROMPT_OVERRIDE, matching how all other +force checks in the codebase work. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit d37c927d9e142e790e1167bbd13a2a753739c72f) +--- + tools/pvchange.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/pvchange.c b/tools/pvchange.c +index 1b43b0154..57491f3d9 100644 +--- a/tools/pvchange.c ++++ b/tools/pvchange.c +@@ -76,7 +76,7 @@ static int _pvchange_single(struct cmd_context *cmd, struct volume_group *vg, + if ((used = is_used_pv(pv)) < 0) + goto_bad; + +- if (used && (arg_count(cmd, force_ARG) != DONT_PROMPT_OVERRIDE)) { ++ if (used && (arg_force_value(cmd) != DONT_PROMPT_OVERRIDE)) { + log_error("PV %s is used by a VG but its metadata is missing.", pv_name); + log_error("Can't change PV '%s' without -ff.", pv_name); + goto bad; +-- +2.54.0 + diff --git a/0172-raid-fix-missing-failure-return-after-reshape-space-.patch b/0172-raid-fix-missing-failure-return-after-reshape-space-.patch new file mode 100644 index 0000000..8ffeeb5 --- /dev/null +++ b/0172-raid-fix-missing-failure-return-after-reshape-space-.patch @@ -0,0 +1,41 @@ +From 3075f9ed8f06136ed8eea5137880ec90adfe38d0 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 17 Apr 2026 20:28:11 +0200 +Subject: [PATCH 172/211] raid: fix missing failure return after reshape space + allocation error + +When lv_extend() fails in _lv_alloc_reshape_space(), the cleanup +function _lv_alloc_reshape_post_extend() restores saved state, +but on success execution falls through to the code path that +assumes the allocation succeeded. + +The bug originates from commit f1b78665 which had +"return _lv_alloc_reshape_post_extend()" - returning the cleanup +result (1=success) as the function result, masking the allocation +failure. A later cleanup commit 1d69fc7c5 converted this to +"if (!...) return_0;" style but missed that fallthrough now +continues into the success path. + +Add the missing "return 0;" after successful cleanup. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 64c70d9a1718b93358414b070d0fd63de4838eaa) +--- + lib/metadata/raid_manip.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lib/metadata/raid_manip.c b/lib/metadata/raid_manip.c +index 99c71e967..a30917fb6 100644 +--- a/lib/metadata/raid_manip.c ++++ b/lib/metadata/raid_manip.c +@@ -1599,6 +1599,7 @@ static int _lv_alloc_reshape_space(struct logical_volume *lv, + display_lvname(lv)); + if (!_lv_alloc_reshape_post_extend(lv, segtype_sav, stripe_size_sav, lv_size_cur)) + return_0; ++ return 0; + } + + /* pay attention to lv_extend maybe having allocated more because of layout specific rounding */ +-- +2.54.0 + diff --git a/0173-integrity-fail-on-lv_remove-error-in-lv_remove_integ.patch b/0173-integrity-fail-on-lv_remove-error-in-lv_remove_integ.patch new file mode 100644 index 0000000..e24fd64 --- /dev/null +++ b/0173-integrity-fail-on-lv_remove-error-in-lv_remove_integ.patch @@ -0,0 +1,44 @@ +From 242dc229e2622a69a0a26d0b636bd1fbc027e7ae Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 17 Apr 2026 22:23:49 +0200 +Subject: [PATCH 173/211] integrity: fail on lv_remove error in + lv_remove_integrity_from_raid + +When lv_remove() of iorig or imeta fails, the code logged an error +but continued to vg_write/vg_commit, persisting inconsistent metadata +with orphaned sub-LVs, and returned success to the caller. + +Return failure immediately so the caller knows the operation did not +complete and the inconsistent in-memory state is not persisted. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 8ef94cc38df8fddf47ca5a42a64a8a5c88bf64c4) +--- + lib/metadata/integrity_manip.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c +index 26b644266..5610f8236 100644 +--- a/lib/metadata/integrity_manip.c ++++ b/lib/metadata/integrity_manip.c +@@ -325,11 +325,15 @@ int lv_remove_integrity_from_raid(struct logical_volume *lv, char **remove_image + + log_debug("Removing unused integrity LVs %s %s", lv_iorig->name, lv_imeta->name); + +- if (!lv_remove(lv_iorig)) ++ if (!lv_remove(lv_iorig)) { + log_error("Failed to remove unused iorig LV %s.", lv_iorig->name); ++ return 0; ++ } + +- if (!lv_remove(lv_imeta)) ++ if (!lv_remove(lv_imeta)) { + log_error("Failed to remove unused imeta LV %s.", lv_imeta->name); ++ return 0; ++ } + } + + if (!vg_write(vg) || !vg_commit(vg)) +-- +2.54.0 + diff --git a/0174-libdm-preserve-format_flags-in-_clone_config_value.patch b/0174-libdm-preserve-format_flags-in-_clone_config_value.patch new file mode 100644 index 0000000..15f4dda --- /dev/null +++ b/0174-libdm-preserve-format_flags-in-_clone_config_value.patch @@ -0,0 +1,31 @@ +From afeb1a290bb97ac6a917edbc0df0c163c5f37258 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 21 Apr 2026 15:52:06 +0200 +Subject: [PATCH 174/211] libdm: preserve format_flags in _clone_config_value + +The format_flags field was not copied when cloning config values, +causing cloned trees to lose formatting metadata (octal, no-quotes, +extra-spaces, array flags) affecting dm_config_flatten and all +dm_config_clone_node callers. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit e904079d3bdfd5efe921c4ffc11b28a40882863c) +--- + libdm/libdm-config.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libdm/libdm-config.c b/libdm/libdm-config.c +index 25b206dbd..0f1b7a6bc 100644 +--- a/libdm/libdm-config.c ++++ b/libdm/libdm-config.c +@@ -1431,6 +1431,7 @@ static struct dm_config_value *_clone_config_value(struct dm_pool *mem, + } + + new_cv->type = v->type; ++ new_cv->format_flags = v->format_flags; + + if (v->next && !(new_cv->next = _clone_config_value(mem, v->next))) + return_NULL; +-- +2.54.0 + diff --git a/0175-lv-check-dm_pool_strdup-return-in-lv_set_creation.patch b/0175-lv-check-dm_pool_strdup-return-in-lv_set_creation.patch new file mode 100644 index 0000000..e54b7ac --- /dev/null +++ b/0175-lv-check-dm_pool_strdup-return-in-lv_set_creation.patch @@ -0,0 +1,33 @@ +From bc855407bd841206443eef82023c18212514f1cf Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Mon, 20 Apr 2026 21:38:02 +0200 +Subject: [PATCH 175/211] lv: check dm_pool_strdup return in lv_set_creation + +When hostname is provided but dm_pool_strdup fails, lv->hostname +is set to NULL and the function returns 1 (success), so the caller +believes hostname was set. Return failure instead. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit b3936fccda11821cdc0b37e0c7146fb189f1916a) +--- + lib/metadata/lv.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c +index 0917cdd4e..90a0c9e86 100644 +--- a/lib/metadata/lv.c ++++ b/lib/metadata/lv.c +@@ -1633,8 +1633,8 @@ int lv_set_creation(struct logical_volume *lv, + } + + lv->hostname = _utsname.nodename; +- } else +- lv->hostname = dm_pool_strdup(lv->vg->vgmem, hostname); ++ } else if (!(lv->hostname = dm_pool_strdup(lv->vg->vgmem, hostname))) ++ return_0; + + lv->timestamp = timestamp ? : (uint64_t) time(NULL); + +-- +2.54.0 + diff --git a/0176-libdm-return-error-for-unknown-value-type-in-_write_.patch b/0176-libdm-return-error-for-unknown-value-type-in-_write_.patch new file mode 100644 index 0000000..a854fc9 --- /dev/null +++ b/0176-libdm-return-error-for-unknown-value-type-in-_write_.patch @@ -0,0 +1,31 @@ +From 3c407234b27a57803a073a8d170cced508c6fe2b Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 21 Apr 2026 15:51:16 +0200 +Subject: [PATCH 176/211] libdm: return error for unknown value type in + _write_value + +The default case logged an error but fell through to return 1 +(success), making serialization silently produce incomplete output. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 51ba4ec56e34fdac37ce02c4e74baef49e000560) +--- + libdm/libdm-config.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/libdm-config.c b/libdm/libdm-config.c +index 0f1b7a6bc..4eb28f79a 100644 +--- a/libdm/libdm-config.c ++++ b/libdm/libdm-config.c +@@ -352,7 +352,7 @@ static int _write_value(struct config_output *out, const struct dm_config_value + + default: + log_error("_write_value: Unknown value type: %d", v->type); +- ++ return 0; + } + + return 1; +-- +2.54.0 + diff --git a/0177-libdm-propagate-return-value-from-recursive-dm_tree_.patch b/0177-libdm-propagate-return-value-from-recursive-dm_tree_.patch new file mode 100644 index 0000000..ce8afa3 --- /dev/null +++ b/0177-libdm-propagate-return-value-from-recursive-dm_tree_.patch @@ -0,0 +1,34 @@ +From aa8700f60ddaf5e4efe2063e6238d8626cdb41a4 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 21 Apr 2026 15:36:07 +0200 +Subject: [PATCH 177/211] libdm: propagate return value from recursive + dm_tree_children_use_uuid + +The recursive call to dm_tree_children_use_uuid had its return +value ignored, so UUID prefix matches found in grandchildren +(or deeper) were silently lost. This caused pv_uses_vg circular +dependency detection to miss multi-level DM stacking scenarios. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit c24bc3973e73d9b0edc9325d42fffc99e92f8361) +--- + libdm/libdm-deptree.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/libdm/libdm-deptree.c b/libdm/libdm-deptree.c +index 2fdd9fa07..66396c4fd 100644 +--- a/libdm/libdm-deptree.c ++++ b/libdm/libdm-deptree.c +@@ -3042,7 +3042,8 @@ int dm_tree_children_use_uuid(struct dm_tree_node *dnode, + return 1; + + if (dm_tree_node_num_children(child, 0)) +- dm_tree_children_use_uuid(child, uuid_prefix, uuid_prefix_len); ++ if (dm_tree_children_use_uuid(child, uuid_prefix, uuid_prefix_len)) ++ return 1; + } + + return 0; +-- +2.54.0 + diff --git a/0178-libdm-add-missing-SEG_RAID10-case-in-dm_tree_node_ad.patch b/0178-libdm-add-missing-SEG_RAID10-case-in-dm_tree_node_ad.patch new file mode 100644 index 0000000..a19d736 --- /dev/null +++ b/0178-libdm-add-missing-SEG_RAID10-case-in-dm_tree_node_ad.patch @@ -0,0 +1,33 @@ +From 4715197d27e6f096748a823daaaedd758ac3eab1 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 21 Apr 2026 15:32:54 +0200 +Subject: [PATCH 178/211] libdm: add missing SEG_RAID10 case in + dm_tree_node_add_null_area + +All other RAID-related switch statements in deptree (e.g. +_emit_areas_line) include SEG_RAID10, but this one was missed. +Without it, adding a null area for a missing RAID10 device +fails with "unsupported segment type" even though the emit +path handles it correctly. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit fbffdf6ad388c926f8a3f198536f08b50f5340b0) +--- + libdm/libdm-deptree.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libdm/libdm-deptree.c b/libdm/libdm-deptree.c +index 66396c4fd..1e3cfbff7 100644 +--- a/libdm/libdm-deptree.c ++++ b/libdm/libdm-deptree.c +@@ -3919,6 +3919,7 @@ int dm_tree_node_add_null_area(struct dm_tree_node *node, uint64_t offset) + case SEG_RAID0: + case SEG_RAID0_META: + case SEG_RAID1: ++ case SEG_RAID10: + case SEG_RAID4: + case SEG_RAID5_N: + case SEG_RAID5_LA: +-- +2.54.0 + diff --git a/0179-libdm-fix-undersized-group_str-buffer-in-dm_stats_st.patch b/0179-libdm-fix-undersized-group_str-buffer-in-dm_stats_st.patch new file mode 100644 index 0000000..e97e992 --- /dev/null +++ b/0179-libdm-fix-undersized-group_str-buffer-in-dm_stats_st.patch @@ -0,0 +1,32 @@ +From 4004d8adb51172eb03af7df9a13c7b705fce8b4c Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 21 Apr 2026 15:20:13 +0200 +Subject: [PATCH 179/211] libdm: fix undersized group_str buffer in + dm_stats_start_filemapd + +group_str[8] can only hold 7-digit numbers but is used to format +a uint64_t group_id which can be up to 20 digits. Increase to 24 +to accommodate the full range. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 9fb8d23fd23db96c728793b0ab692c80620fc666) +--- + libdm/libdm-stats.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/libdm-stats.c b/libdm/libdm-stats.c +index 6e6d63a8b..ee5e2a181 100644 +--- a/libdm/libdm-stats.c ++++ b/libdm/libdm-stats.c +@@ -5048,7 +5048,7 @@ int dm_stats_start_filemapd(int fd, uint64_t group_id, const char *path, + dm_filemapd_mode_t mode, unsigned foreground, + unsigned verbose) + { +- char fd_str[8], group_str[8], fg_str[2], verb_str[2]; ++ char fd_str[8], group_str[24], fg_str[2], verb_str[2]; + const char *mode_str = _filemapd_mode_names[mode]; + char *args[NR_FILEMAPD_ARGS + 1]; + pid_t pid = 0; +-- +2.54.0 + diff --git a/0180-libdm-decode-DM_STATS_WALK_GROUP-flag-before-using-g.patch b/0180-libdm-decode-DM_STATS_WALK_GROUP-flag-before-using-g.patch new file mode 100644 index 0000000..5f08b98 --- /dev/null +++ b/0180-libdm-decode-DM_STATS_WALK_GROUP-flag-before-using-g.patch @@ -0,0 +1,54 @@ +From 7f021dfbb01a635d3e89ca4805d780d1fef6c8e2 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 21 Apr 2026 15:19:40 +0200 +Subject: [PATCH 180/211] libdm: decode DM_STATS_WALK_GROUP flag before using + group_id in set_alias + +The flag-stripping logic for DM_STATS_WALK_GROUP was placed after +the _stats_region_is_grouped() check, which uses group_id as an +array index into dms->regions[]. When called with a flagged +group_id the raw value (with bit 0x4000000000000 set) would cause +an out-of-bounds access. + +Move the flag decoding before the grouped check, matching the +order used by dm_stats_get_alias(). + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 4117d8a2d32acfb64bfcad5c905d02ae648cacc7) +--- + libdm/libdm-stats.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/libdm/libdm-stats.c b/libdm/libdm-stats.c +index ee5e2a181..969f75372 100644 +--- a/libdm/libdm-stats.c ++++ b/libdm/libdm-stats.c +@@ -3284,12 +3284,6 @@ int dm_stats_set_alias(struct dm_stats *dms, uint64_t group_id, const char *alia + if (!dms->regions || !dms->groups || !alias) + return_0; + +- if (!_stats_region_is_grouped(dms, group_id)) { +- log_error("Cannot set alias for ungrouped region ID " +- FMTu64, group_id); +- return 0; +- } +- + if (group_id & DM_STATS_WALK_GROUP) { + if (group_id == DM_STATS_WALK_GROUP) + group_id = dms->cur_group; +@@ -3297,6 +3291,12 @@ int dm_stats_set_alias(struct dm_stats *dms, uint64_t group_id, const char *alia + group_id &= ~DM_STATS_WALK_GROUP; + } + ++ if (!_stats_region_is_grouped(dms, group_id)) { ++ log_error("Cannot set alias for ungrouped region ID " ++ FMTu64, group_id); ++ return 0; ++ } ++ + if (group_id != dms->regions[group_id].group_id) { + /* dm_stats_set_alias() must be called on the group ID. */ + log_error("Cannot set alias for group member " FMTu64 ".", +-- +2.54.0 + diff --git a/0181-libdm-fix-dm_pool_strndup-over-allocation.patch b/0181-libdm-fix-dm_pool_strndup-over-allocation.patch new file mode 100644 index 0000000..2f434e8 --- /dev/null +++ b/0181-libdm-fix-dm_pool_strndup-over-allocation.patch @@ -0,0 +1,32 @@ +From 3b3354722e74cbb238fedc5e508a7b0f4559ba97 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 19:36:08 +0200 +Subject: [PATCH 181/211] libdm: fix dm_pool_strndup over-allocation + +dm_pool_strndup allocated n + 1 bytes regardless of actual +string length, wasting pool memory when the string is shorter +than n. Allocate len + 1 instead, matching strndup semantics +where only the needed bytes are reserved. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 2b0fb7d970347485de7df163da0ac631ffac1918) +--- + libdm/mm/pool.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/mm/pool.c b/libdm/mm/pool.c +index 9b3f2d2df..ad487c174 100644 +--- a/libdm/mm/pool.c ++++ b/libdm/mm/pool.c +@@ -61,7 +61,7 @@ char *dm_pool_strndup(struct dm_pool *p, const char *str, size_t n) + { + size_t slen = strlen(str); + size_t len = (slen < n) ? slen : n; +- char *ret = dm_pool_alloc(p, n + 1); ++ char *ret = dm_pool_alloc(p, len + 1); + + if (ret) { + ret[len] = '\0'; +-- +2.54.0 + diff --git a/0182-dmeventd-reject-zero-length-daemon-response.patch b/0182-dmeventd-reject-zero-length-daemon-response.patch new file mode 100644 index 0000000..b879358 --- /dev/null +++ b/0182-dmeventd-reject-zero-length-daemon-response.patch @@ -0,0 +1,47 @@ +From 32ef78b9921da0cda3cd7c9512f6d4b0eee43c62 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Wed, 22 Apr 2026 13:52:52 +0200 +Subject: [PATCH 182/211] dmeventd: reject zero-length daemon response + +When the daemon sends a response with size == 0, _daemon_read +returned success with msg->data == NULL. The caller then passed +the NULL pointer to _check_message_id which dereferences it via +sscanf. Treat zero-length payload as invalid since a valid +response always carries at least pid:seqnr. Also check msg->data +in daemon_talk to make the NULL guard visible to static analysis. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 963a72e83e33bc777e6494dfffdeec451fbf9671) +--- + daemons/dmeventd/libdevmapper-event.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/daemons/dmeventd/libdevmapper-event.c b/daemons/dmeventd/libdevmapper-event.c +index 36f119261..67fe61939 100644 +--- a/daemons/dmeventd/libdevmapper-event.c ++++ b/daemons/dmeventd/libdevmapper-event.c +@@ -263,8 +263,10 @@ static int _daemon_read(struct dm_event_fifos *fifos, + msg->cmd = ntohl(header[0]); + bytes = 0; + +- if (!(size = msg->size = ntohl(header[1]))) +- break; ++ if (!(size = msg->size = ntohl(header[1]))) { ++ log_error("Missing event server response data."); ++ goto bad; ++ } + + if (!(buf = msg->data = malloc(msg->size))) { + log_error("Unable to allocate message data."); +@@ -389,7 +391,7 @@ int daemon_talk(struct dm_event_fifos *fifos, + free(msg->data); + msg->data = NULL; + +- if (!_daemon_read(fifos, msg)) { ++ if (!_daemon_read(fifos, msg) || !msg->data) { + stack; + return -EIO; + } +-- +2.54.0 + diff --git a/0183-dmeventd-treat-ENODEV-same-as-ENXIO-in-event-wait.patch b/0183-dmeventd-treat-ENODEV-same-as-ENXIO-in-event-wait.patch new file mode 100644 index 0000000..86798c7 --- /dev/null +++ b/0183-dmeventd-treat-ENODEV-same-as-ENXIO-in-event-wait.patch @@ -0,0 +1,28 @@ +From c92aa21b94f6c48fdbe6143e6bb72fa2d91d4467 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 17 Apr 2026 00:10:25 +0200 +Subject: [PATCH 183/211] dmeventd: treat ENODEV same as ENXIO in event wait + +Handle ENODEV in _event_wait() to properly detach monitoring +when a device disappears, instead of entering a retry loop. + +(cherry picked from commit 87d3190f5ffd6217166a9cec8f8261f83d89f10e) +--- + daemons/dmeventd/dmeventd.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/daemons/dmeventd/dmeventd.c b/daemons/dmeventd/dmeventd.c +index 1ae5ef629..b28b1e78d 100644 +--- a/daemons/dmeventd/dmeventd.c ++++ b/daemons/dmeventd/dmeventd.c +@@ -912,6 +912,7 @@ static int _event_wait(struct thread_status *thread) + dm_task_set_event_nr(thread->wait_task, info.event_nr); + } else { + switch (dm_task_get_errno(thread->wait_task)) { ++ case ENODEV: + case ENXIO: + disappeared: + log_error("%s disappeared, detaching.", +-- +2.54.0 + diff --git a/0184-dmfilemapd-fix-fd-validity-check-from-0-to-0.patch b/0184-dmfilemapd-fix-fd-validity-check-from-0-to-0.patch new file mode 100644 index 0000000..292bbb3 --- /dev/null +++ b/0184-dmfilemapd-fix-fd-validity-check-from-0-to-0.patch @@ -0,0 +1,30 @@ +From 0668105f1995189b5abc1aba2a352ffe65f7645c Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Mon, 20 Apr 2026 17:31:33 +0200 +Subject: [PATCH 184/211] dmfilemapd: fix fd validity check from > 0 to >= 0 + +File descriptor 0 is valid (stdin). The adjacent close() at line 615 +already correctly uses fd >= 0, but the mismatch check used fd > 0. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 9d5912373c269a69c08823d1513f0ebf94985364) +--- + libdm/dm-tools/dmfilemapd.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libdm/dm-tools/dmfilemapd.c b/libdm/dm-tools/dmfilemapd.c +index 37fbc4e0e..f8456df6f 100644 +--- a/libdm/dm-tools/dmfilemapd.c ++++ b/libdm/dm-tools/dmfilemapd.c +@@ -618,7 +618,7 @@ check_unlinked: + return 0; + + /* Should not happen with normal /proc. */ +- if ((fd > 0) && !same) { ++ if ((fd >= 0) && !same) { + log_error("File descriptor mismatch: %d and %s (read from %s) " + "are not the same file!", fm->fd, link_buf, path_buf); + return 0; +-- +2.54.0 + diff --git a/0185-device-fix-fd-validity-check-to-accept-fd-0.patch b/0185-device-fix-fd-validity-check-to-accept-fd-0.patch new file mode 100644 index 0000000..961f7c3 --- /dev/null +++ b/0185-device-fix-fd-validity-check-to-accept-fd-0.patch @@ -0,0 +1,40 @@ +From a63978a57d202ba5ec7b2afb9e6f6729bcc16941 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 20:04:06 +0200 +Subject: [PATCH 185/211] device: fix fd validity check to accept fd 0 + +File descriptor 0 is valid; the check should be fd < 0 to detect +uninitialized bcache_fd (-1), not fd <= 0 which incorrectly rejects +fd 0. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 15b6ed433e273d008bad6ca845ca69f41896cc0c) +--- + lib/device/dev-io.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/lib/device/dev-io.c b/lib/device/dev-io.c +index ab00ba677..efac37b3e 100644 +--- a/lib/device/dev-io.c ++++ b/lib/device/dev-io.c +@@ -99,7 +99,7 @@ static int _dev_get_size_dev(struct device *dev, uint64_t *size) + return 1; + } + +- if (fd <= 0) { ++ if (fd < 0) { + if (!dev_open_readonly_quiet(dev)) + return_0; + fd = dev_fd(dev); +@@ -202,7 +202,7 @@ int dev_get_direct_block_sizes(struct device *dev, unsigned int *physical_block_ + return 1; + } + +- if (fd <= 0) { ++ if (fd < 0) { + if (!dev_open_readonly_quiet(dev)) + return 0; + fd = dev_fd(dev); +-- +2.54.0 + diff --git a/0186-lvmlockd-fix-off-by-one-in-match_dm_uuid-lv-uuid-ext.patch b/0186-lvmlockd-fix-off-by-one-in-match_dm_uuid-lv-uuid-ext.patch new file mode 100644 index 0000000..aed7fe4 --- /dev/null +++ b/0186-lvmlockd-fix-off-by-one-in-match_dm_uuid-lv-uuid-ext.patch @@ -0,0 +1,36 @@ +From 780adf132a5e80ddf5e57cbac5d9dadeddff9eb5 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 11:58:35 +0200 +Subject: [PATCH 186/211] lvmlockd: fix off-by-one in match_dm_uuid lv uuid + extraction + +The loop extracting the LV UUID from the DM UUID string copied +33 characters (positions 36-68) instead of the correct 32 +(positions 36-67). The DM UUID format is "LVM-" (4) + +vg_uuid (32) + lv_uuid (32), so the LV UUID occupies positions +36 through 67. The extra byte at position 68 could be a layer +separator '-' or null terminator, causing spurious match failures +for LVs with DM layer suffixes during lock adoption. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit a492a9c66482a4e95910ff0db2e655eed6245114) +--- + daemons/lvmlockd/lvmlockd-core.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/daemons/lvmlockd/lvmlockd-core.c b/daemons/lvmlockd/lvmlockd-core.c +index b3960feb7..406c88396 100644 +--- a/daemons/lvmlockd/lvmlockd-core.c ++++ b/daemons/lvmlockd/lvmlockd-core.c +@@ -5641,7 +5641,7 @@ static int match_dm_uuid(char *dm_uuid, char *lv_lock_uuid) + j++; + } + +- for (i = 36, j = 0; i < 69; i++) { ++ for (i = 36, j = 0; i < 68; i++) { + buf2[j] = dm_uuid[i]; + j++; + } +-- +2.54.0 + diff --git a/0187-lvmlockd-fix-lockspace-use-after-mutex-unlock-in-wor.patch b/0187-lvmlockd-fix-lockspace-use-after-mutex-unlock-in-wor.patch new file mode 100644 index 0000000..91a665e --- /dev/null +++ b/0187-lvmlockd-fix-lockspace-use-after-mutex-unlock-in-wor.patch @@ -0,0 +1,44 @@ +From bbc86311afbcc9ecb04d84948b83df1a5f583c9b Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 11:43:21 +0200 +Subject: [PATCH 187/211] lvmlockd: fix lockspace use after mutex unlock in + work_init_vg + +The log_error at the EEXIST check accessed ls->name and +ls->vg_name after releasing lockspaces_mutex. Another thread +could free the lockspace between the unlock and the access. + +Move the log_error before the mutex unlock so the lockspace +struct is accessed only while the lock is held. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 149bce1bfda1506e88dc5c6efd2388c1dec7c428) +--- + daemons/lvmlockd/lvmlockd-core.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/daemons/lvmlockd/lvmlockd-core.c b/daemons/lvmlockd/lvmlockd-core.c +index 406c88396..a551e55fc 100644 +--- a/daemons/lvmlockd/lvmlockd-core.c ++++ b/daemons/lvmlockd/lvmlockd-core.c +@@ -3691,13 +3691,13 @@ static int work_init_vg(struct action *act) + break; + } + } +- pthread_mutex_unlock(&lockspaces_mutex); +- +- if (rv == -EEXIST) { ++ if (rv == -EEXIST) + log_error("Existing lockspace name %s matches new %s VG names %s %s", + ls->name, ls_name, ls->vg_name, act->vg_name); ++ pthread_mutex_unlock(&lockspaces_mutex); ++ ++ if (rv == -EEXIST) + return rv; +- } + + if (act->lm_type == LD_LM_SANLOCK) + rv = lm_init_vg_sanlock(ls_name, act->vg_name, act->flags, act->vg_args, act->align_mb); +-- +2.54.0 + diff --git a/0188-lvmlockd-fix-off-by-one-in-_owner_str-snprintf-size.patch b/0188-lvmlockd-fix-off-by-one-in-_owner_str-snprintf-size.patch new file mode 100644 index 0000000..864c82b --- /dev/null +++ b/0188-lvmlockd-fix-off-by-one-in-_owner_str-snprintf-size.patch @@ -0,0 +1,30 @@ +From 2fb5690a170a93ab185202d69673cd99bedfdf9d Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 13:35:33 +0200 +Subject: [PATCH 188/211] lvmlockd: fix off-by-one in _owner_str snprintf size + +snprintf already NUL-terminates within the given size, so +passing sizeof()-1 wastes one byte of the buffer. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit ad69fff126f7ff7c19a666bdc95822b0fc79b734) +--- + lib/locking/lvmlockd.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/locking/lvmlockd.c b/lib/locking/lvmlockd.c +index af827e49e..c74a9a1d3 100644 +--- a/lib/locking/lvmlockd.c ++++ b/lib/locking/lvmlockd.c +@@ -149,7 +149,7 @@ static char *_owner_str(struct owner *owner) + + /* Use a --lockopt setting to print all owner details? */ + +- snprintf(log_owner_str, sizeof(log_owner_str)-1, " (host_id %u)", owner->host_id); ++ snprintf(log_owner_str, sizeof(log_owner_str), " (host_id %u)", owner->host_id); + return log_owner_str; + } + +-- +2.54.0 + diff --git a/0189-lvmlockd-fix-null-pointer-arithmetic-in-read_host_id.patch b/0189-lvmlockd-fix-null-pointer-arithmetic-in-read_host_id.patch new file mode 100644 index 0000000..bc37023 --- /dev/null +++ b/0189-lvmlockd-fix-null-pointer-arithmetic-in-read_host_id.patch @@ -0,0 +1,37 @@ +From 00c5f17b125dc803a0f1c5ae509f9998221d1321 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 11:36:30 +0200 +Subject: [PATCH 189/211] lvmlockd: fix null pointer arithmetic in + read_host_id_file + +val was computed as sep + 1 before checking if sep was NULL, +causing undefined behavior. The subsequent !val check was +also useless since NULL + 1 is never NULL. Move the null +check before the pointer arithmetic. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 1a6da2dcf6f41390ca3f7e84b11e6d10aeff2068) +--- + daemons/lvmlockd/lvmlockd-sanlock.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/daemons/lvmlockd/lvmlockd-sanlock.c b/daemons/lvmlockd/lvmlockd-sanlock.c +index 2a5720b07..62765c4d1 100644 +--- a/daemons/lvmlockd/lvmlockd-sanlock.c ++++ b/daemons/lvmlockd/lvmlockd-sanlock.c +@@ -304,10 +304,9 @@ static int read_host_id_file(void) + + key = line; + sep = strstr(line, "="); +- val = sep + 1; +- +- if (!sep || !val) ++ if (!sep) + continue; ++ val = sep + 1; + + *sep = '\0'; + memset(key_str, 0, sizeof(key_str)); +-- +2.54.0 + diff --git a/0190-lvmlockd-fix-_free_vg-ignoring-error-result.patch b/0190-lvmlockd-fix-_free_vg-ignoring-error-result.patch new file mode 100644 index 0000000..8190d6f --- /dev/null +++ b/0190-lvmlockd-fix-_free_vg-ignoring-error-result.patch @@ -0,0 +1,33 @@ +From c488a31560b7083d2cc74a4afdad0662136182a2 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 13:26:32 +0200 +Subject: [PATCH 190/211] lvmlockd: fix _free_vg ignoring error result + +_free_vg() computes ret based on the lvmlockd result but +unconditionally returns 1, silently swallowing all errors +from _free_vg_dlm() and _free_vg_idm() callers. + +Return ret instead. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit fd6ca1376b3af4077e1e9ecf088c534cfe97f84d) +--- + lib/locking/lvmlockd.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/locking/lvmlockd.c b/lib/locking/lvmlockd.c +index c74a9a1d3..c4754c789 100644 +--- a/lib/locking/lvmlockd.c ++++ b/lib/locking/lvmlockd.c +@@ -1136,7 +1136,7 @@ static int _free_vg(struct cmd_context *cmd, struct volume_group *vg) + + daemon_reply_destroy(reply); + +- return 1; ++ return ret; + } + + static int _free_vg_dlm(struct cmd_context *cmd, struct volume_group *vg) +-- +2.54.0 + diff --git a/0191-lvmlockd-use-CLOCK_MONOTONIC-for-pthread_cond_timedw.patch b/0191-lvmlockd-use-CLOCK_MONOTONIC-for-pthread_cond_timedw.patch new file mode 100644 index 0000000..aef7b3e --- /dev/null +++ b/0191-lvmlockd-use-CLOCK_MONOTONIC-for-pthread_cond_timedw.patch @@ -0,0 +1,62 @@ +From c556798f2120614577938d302fdb0f33b2835a00 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Tue, 14 Apr 2026 21:51:23 +0200 +Subject: [PATCH 191/211] lvmlockd: use CLOCK_MONOTONIC for + pthread_cond_timedwait + +Switch worker_thread_main() from CLOCK_REALTIME to CLOCK_MONOTONIC +so the worker timeout is immune to wall-clock jumps (NTP corrections, +manual date changes). + +Initialize worker_cond with pthread_condattr_setclock(CLOCK_MONOTONIC) +to match. Add _pthread_cond_init() helper matching the dmeventd +pattern from commit 2c8a26423. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit af98bb6415a5204d43daeca23c8e00fdd55b1473) +--- + daemons/lvmlockd/lvmlockd-core.c | 14 ++++++++++++-- + 1 file changed, 12 insertions(+), 2 deletions(-) + +diff --git a/daemons/lvmlockd/lvmlockd-core.c b/daemons/lvmlockd/lvmlockd-core.c +index a551e55fc..a9d03bc26 100644 +--- a/daemons/lvmlockd/lvmlockd-core.c ++++ b/daemons/lvmlockd/lvmlockd-core.c +@@ -303,6 +303,16 @@ static const char *_syslog_num_to_name(int num) + return "unknown"; + } + ++static void _pthread_cond_init(pthread_cond_t *cond) ++{ ++ pthread_condattr_t cattr; ++ ++ pthread_condattr_init(&cattr); ++ pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC); ++ pthread_cond_init(cond, &cattr); ++ pthread_condattr_destroy(&cattr); ++} ++ + static uint64_t monotime(void) + { + struct timespec ts; +@@ -3857,7 +3867,7 @@ static void *worker_thread_main(void *arg_in) + + while (1) { + pthread_mutex_lock(&worker_mutex); +- if (clock_gettime(CLOCK_REALTIME, &ts)) { ++ if (clock_gettime(CLOCK_MONOTONIC, &ts)) { + log_error("clock_gettime failed."); + ts.tv_sec = ts.tv_nsec = 0; + } +@@ -4059,7 +4069,7 @@ static int setup_worker_thread(void) + INIT_LIST_HEAD(&worker_list); + + pthread_mutex_init(&worker_mutex, NULL); +- pthread_cond_init(&worker_cond, NULL); ++ _pthread_cond_init(&worker_cond); + + rv = pthread_create(&worker_thread, NULL, worker_thread_main, NULL); + if (rv) +-- +2.54.0 + diff --git a/0192-lvconvert-fix-dm_strncpy-off-by-one-in-_raid_split_i.patch b/0192-lvconvert-fix-dm_strncpy-off-by-one-in-_raid_split_i.patch new file mode 100644 index 0000000..3775a4b --- /dev/null +++ b/0192-lvconvert-fix-dm_strncpy-off-by-one-in-_raid_split_i.patch @@ -0,0 +1,38 @@ +From e5cf4b66c6a319fd516224a8be6b8bdf174f60ce Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 14:52:40 +0200 +Subject: [PATCH 192/211] lvconvert: fix dm_strncpy off-by-one in + _raid_split_image_conversion + +dm_strncpy(dest, src, n) treats n as the buffer size and copies +at most n-1 characters plus a NUL terminator. Passing +(s - lv->name) truncates the RAID LV name by one character, +causing find_lv() to always fail for the parent LV lookup. + +For example, with name "foo_rimage_0", s - lv->name = 3, so +dm_strncpy copies only 2 chars giving "fo" instead of "foo". + +Pass s - lv->name + 1 to include room for NUL terminator. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 0b4a4d265e67fcd7f953da403b141bbacf556499) +--- + tools/lvconvert.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index 7dc8a3736..5c5597b50 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -1182,7 +1182,7 @@ static int _raid_split_image_conversion(struct logical_volume *lv) + + if (lv_is_raid_image(lv) && + (s = strstr(lv->name, "_rimage_"))) { +- dm_strncpy(raidlv_name, lv->name, s - lv->name); ++ dm_strncpy(raidlv_name, lv->name, s - lv->name + 1); + + if (!(tmp_lv = find_lv(lv->vg, raidlv_name))) { + log_error("Failed to find RaidLV of RAID subvolume %s.", +-- +2.54.0 + diff --git a/0193-lvcreate-fix-recovery-rate-check-when-max-is-unset.patch b/0193-lvcreate-fix-recovery-rate-check-when-max-is-unset.patch new file mode 100644 index 0000000..6a00270 --- /dev/null +++ b/0193-lvcreate-fix-recovery-rate-check-when-max-is-unset.patch @@ -0,0 +1,35 @@ +From 8501f437d4c0790c65d7b18525390baa3b168f47 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 15:14:09 +0200 +Subject: [PATCH 193/211] lvcreate: fix recovery rate check when max is unset + +Without the guard, specifying only --minrecoveryrate without +--maxrecoveryrate caused max_recovery_rate (defaulting to 0, meaning +no limit) to be incorrectly capped to min_recovery_rate. + +Match the logic used in lvchange and merge validation which both +check max_recovery_rate is non-zero before comparing. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 969ba3c562dbe65cf52c81a51bf700e551329881) +--- + tools/lvcreate.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/tools/lvcreate.c b/tools/lvcreate.c +index ef6875f55..e9a242ccb 100644 +--- a/tools/lvcreate.c ++++ b/tools/lvcreate.c +@@ -594,7 +594,8 @@ static int _read_raid_params(struct cmd_context *cmd, + lp->min_recovery_rate = arg_uint_value(cmd, minrecoveryrate_ARG, 0) / 2; + lp->max_recovery_rate = arg_uint_value(cmd, maxrecoveryrate_ARG, 0) / 2; + +- if (lp->min_recovery_rate > lp->max_recovery_rate) { ++ if (lp->max_recovery_rate && ++ lp->min_recovery_rate > lp->max_recovery_rate) { + log_print_unless_silent("Minimum recovery rate cannot be higher than maximum, adjusting."); + lp->max_recovery_rate = lp->min_recovery_rate; + } +-- +2.54.0 + diff --git a/0194-lvconvert-add-missing-archive-in-_cache_vol_attach.patch b/0194-lvconvert-add-missing-archive-in-_cache_vol_attach.patch new file mode 100644 index 0000000..ef513f7 --- /dev/null +++ b/0194-lvconvert-add-missing-archive-in-_cache_vol_attach.patch @@ -0,0 +1,33 @@ +From 721eb5f7d48b00d540a9233fcc64a6b54e3e27c4 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 19 Apr 2026 17:01:53 +0200 +Subject: [PATCH 194/211] lvconvert: add missing archive in _cache_vol_attach + +_cache_vol_attach modifies VG metadata (renames LV, creates +cache, writes VG) but did not call archive() to create a +metadata backup before changes, unlike the parallel function +_cache_pool_attach which does. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 4c3086347c1c89853271f9071d05d4bedf4adab7) +--- + tools/lvconvert.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index 5c5597b50..0b3b4bad8 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -3643,6 +3643,9 @@ static int _cache_vol_attach(struct cmd_context *cmd, + if (!get_cache_params(cmd, &chunk_size, &cache_metadata_format, &cache_mode, &policy_name, &policy_settings)) + goto_out; + ++ if (!archive(lv->vg)) ++ goto_out; ++ + /* + * lv/cache_lv keeps the same lockd lock it had before, the lock for + * lv_fast is kept but is not used while it's attached, and +-- +2.54.0 + diff --git a/0195-toolcontext-fix-_set_time_format-to-advance-past-for.patch b/0195-toolcontext-fix-_set_time_format-to-advance-past-for.patch new file mode 100644 index 0000000..1db9218 --- /dev/null +++ b/0195-toolcontext-fix-_set_time_format-to-advance-past-for.patch @@ -0,0 +1,34 @@ +From f668263eb9fd769af988da3b4b62cb6b021ea28b Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sat, 18 Apr 2026 21:44:51 +0200 +Subject: [PATCH 195/211] toolcontext: fix _set_time_format to advance past + format specifier + +After validating a %-format sequence, p_fmt was not advanced past +the format character. On the next loop iteration the same character +was re-processed via the isprint() branch. For most specifiers this +was merely inefficient, but for "%%" it caused false rejection: +the second '%' re-entered the format branch, read '\0', and failed +validation - even though "%%" is a valid strftime literal-percent. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 380f712bb50b3ad4882c1acda2e24250560b6c27) +--- + lib/commands/toolcontext.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c +index 30fc5d4f2..2c8d6896d 100644 +--- a/lib/commands/toolcontext.c ++++ b/lib/commands/toolcontext.c +@@ -538,6 +538,7 @@ static const char *_set_time_format(struct cmd_context *cmd) + } + if (!chars_to_check[i]) + goto_bad; ++ p_fmt++; + } + else if (isprint(c)) + p_fmt++; +-- +2.54.0 + diff --git a/0196-integrity-pass-vg-extent_size-in-lv_extend_integrity.patch b/0196-integrity-pass-vg-extent_size-in-lv_extend_integrity.patch new file mode 100644 index 0000000..adc9307 --- /dev/null +++ b/0196-integrity-pass-vg-extent_size-in-lv_extend_integrity.patch @@ -0,0 +1,34 @@ +From 4c32491b0cdd8d1d87ba6f675a4cb873c087854b Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 17 Apr 2026 22:17:44 +0200 +Subject: [PATCH 196/211] integrity: pass vg extent_size in + lv_extend_integrity_in_raid + +_lv_size_bytes_to_integrity_meta_bytes() uses extent_size with +dm_round_up() when journal_sectors is non-zero. Passing 0 as +extent_size would cause a divide-by-zero if journal_sectors were +ever changed from the hardcoded 0. Pass vg->extent_size to match +the call in _lv_create_integrity_metadata(). + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit baa72bbfd9ae03b5d4f1028aa4c1a25e7618714c) +--- + lib/metadata/integrity_manip.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c +index 5610f8236..c741b54b3 100644 +--- a/lib/metadata/integrity_manip.c ++++ b/lib/metadata/integrity_manip.c +@@ -195,7 +195,7 @@ int lv_extend_integrity_in_raid(struct logical_volume *lv, struct dm_list *pvh) + } + + lv_size_bytes = lv_iorig->size * 512; +- meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv_size_bytes, 0, 0); ++ meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv_size_bytes, 0, vg->extent_size); + meta_sectors = meta_bytes / 512; + meta_extents = meta_sectors / vg->extent_size; + if (!meta_extents) +-- +2.54.0 + diff --git a/0197-pool-fix-fragile-_pmspare-suffix-stripping.patch b/0197-pool-fix-fragile-_pmspare-suffix-stripping.patch new file mode 100644 index 0000000..832e271 --- /dev/null +++ b/0197-pool-fix-fragile-_pmspare-suffix-stripping.patch @@ -0,0 +1,34 @@ +From cbe6880be9acbd9aa8281e5d1e7e0e935c203918 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 17 Apr 2026 21:36:11 +0200 +Subject: [PATCH 197/211] pool: fix fragile _pmspare suffix stripping + +Use strrchr and verify the suffix is exactly "_pmspare" instead of +truncating at the first underscore via strchr, which would produce +a wrong name if the original LV name contained underscores. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 4192f7dafb842b385043244f03cbb0838ebc8aaa) +--- + lib/metadata/pool_manip.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/lib/metadata/pool_manip.c b/lib/metadata/pool_manip.c +index eb081d978..c144b5ee3 100644 +--- a/lib/metadata/pool_manip.c ++++ b/lib/metadata/pool_manip.c +@@ -904,8 +904,9 @@ int vg_remove_pool_metadata_spare(struct volume_group *vg) + + /* Cut off suffix _pmspare */ + if (!_dm_strncpy(new_name, lv->name, sizeof(new_name)) || +- !(c = strchr(new_name, '_'))) { +- log_error(INTERNAL_ERROR "LV %s has no suffix for pool metadata spare.", ++ !(c = strrchr(new_name, '_')) || ++ strcmp(c, "_pmspare")) { ++ log_error(INTERNAL_ERROR "LV %s has no _pmspare suffix.", + display_lvname(lv)); + return 0; + } +-- +2.54.0 + diff --git a/0198-toolcontext-fix-init_run_by_dmeventd-to-return-succe.patch b/0198-toolcontext-fix-init_run_by_dmeventd-to-return-succe.patch new file mode 100644 index 0000000..e41e05b --- /dev/null +++ b/0198-toolcontext-fix-init_run_by_dmeventd-to-return-succe.patch @@ -0,0 +1,33 @@ +From 7391ede7b3c39466a12c1bf21ea933cd08788ad7 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sat, 18 Apr 2026 21:45:40 +0200 +Subject: [PATCH 198/211] toolcontext: fix init_run_by_dmeventd to return + success + +The function unconditionally returned 0 (failure) despite always +succeeding. All current callers ignore the return value, but any +future caller checking it would incorrectly treat the call as failed. +Return 1 to match the convention used by other init_* functions. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit fff49c48094d8565ef7face41297cdd1442bc33e) +--- + lib/commands/toolcontext.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c +index 2c8d6896d..c6e28c2ac 100644 +--- a/lib/commands/toolcontext.c ++++ b/lib/commands/toolcontext.c +@@ -1609,7 +1609,7 @@ int init_run_by_dmeventd(struct cmd_context *cmd) + init_disable_dmeventd_monitoring(1); /* Lock settings */ + cmd->run_by_dmeventd = 1; + +- return 0; ++ return 1; + } + + void destroy_config_context(struct cmd_context *cmd) +-- +2.54.0 + diff --git a/0199-label-fix-_in_bcache-returning-NULL-instead-of-false.patch b/0199-label-fix-_in_bcache-returning-NULL-instead-of-false.patch new file mode 100644 index 0000000..7aac152 --- /dev/null +++ b/0199-label-fix-_in_bcache-returning-NULL-instead-of-false.patch @@ -0,0 +1,31 @@ +From 7b6027b5f8d1cbef6833e14ebae7847108b18cff Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sat, 18 Apr 2026 20:36:13 +0200 +Subject: [PATCH 199/211] label: fix _in_bcache returning NULL instead of false + +The function has return type bool but used 'return NULL' for the +NULL device check. While this works by accident since NULL is 0 +(false), it is a type error - use the correct boolean value. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 076ffd7187ac4e9d19d175bc9ccc199c418ef2aa) +--- + lib/label/label.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/label/label.c b/lib/label/label.c +index d114c8e42..7520847aa 100644 +--- a/lib/label/label.c ++++ b/lib/label/label.c +@@ -261,7 +261,7 @@ static struct bcache *scan_bcache; + static bool _in_bcache(struct device *dev) + { + if (!dev) +- return NULL; ++ return false; + return (dev->flags & DEV_IN_BCACHE) ? true : false; + } + +-- +2.54.0 + diff --git a/0200-mirror-match-alloca-size-with-memset-in-_form_mirror.patch b/0200-mirror-match-alloca-size-with-memset-in-_form_mirror.patch new file mode 100644 index 0000000..0be4ec7 --- /dev/null +++ b/0200-mirror-match-alloca-size-with-memset-in-_form_mirror.patch @@ -0,0 +1,30 @@ +From 92f312fb4792d0b7b0691c7a658d577ad7ea6038 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sat, 18 Apr 2026 01:56:53 +0200 +Subject: [PATCH 200/211] mirror: match alloca size with memset in _form_mirror + +alloca allocated mirrors + 1 slots but memset only zeroed mirrors. +The extra slot was never accessed, so allocate exactly mirrors. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 246f8e663a81e71a3f194324806d8a3a0346b9a5) +--- + lib/metadata/mirror.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/metadata/mirror.c b/lib/metadata/mirror.c +index aee95a6bf..e9e13fdb5 100644 +--- a/lib/metadata/mirror.c ++++ b/lib/metadata/mirror.c +@@ -1627,7 +1627,7 @@ static int _form_mirror(struct cmd_context *cmd, struct alloc_handle *ah, + /* + * create mirror image LVs + */ +- img_lvs = alloca(sizeof(*img_lvs) * (mirrors + 1)); ++ img_lvs = alloca(sizeof(*img_lvs) * mirrors); + memset(img_lvs, 0, sizeof(*img_lvs) * mirrors); + + if (!_create_mimage_lvs(ah, mirrors, stripes, stripe_size, lv, img_lvs, log)) +-- +2.54.0 + diff --git a/0201-memlock-handle-ENOMEM-gracefully-for-anonymous-regio.patch b/0201-memlock-handle-ENOMEM-gracefully-for-anonymous-regio.patch new file mode 100644 index 0000000..7a3c375 --- /dev/null +++ b/0201-memlock-handle-ENOMEM-gracefully-for-anonymous-regio.patch @@ -0,0 +1,44 @@ +From 26ec1446e3c2ea7e8587d46430c88d3069a871d3 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 9 Apr 2026 16:07:39 +0200 +Subject: [PATCH 201/211] memlock: handle ENOMEM gracefully for anonymous + regions + +Anonymous memory regions (thread stacks, heap) can fail mlock with ENOMEM +when trying to lock uncommitted pages - /proc/self/maps shows the full +reserved size (e.g., 8MB stack), but only committed pages can be locked. + +Handle ENOMEM gracefully for anonymous regions to lock committed pages +while tolerating failures on the uncommitted portion. Thread stacks MUST +be locked to prevent swap-related deadlocks on overloaded systems. + +Co-Authored-By: Claude Sonnet 4.5 +(cherry picked from commit 6b0782c5351816a8e580d31ccfdeae1f10eacfad) +--- + lib/mm/memlock.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/lib/mm/memlock.c b/lib/mm/memlock.c +index 0e43bf624..435c2e20e 100644 +--- a/lib/mm/memlock.c ++++ b/lib/mm/memlock.c +@@ -302,6 +302,16 @@ static int _maps_line(const struct dm_config_node *cn, lvmlock_t lock, + + if (lock == LVM_MLOCK) { + if (mlock((const void*)from, sz) < 0) { ++ /* ++ * Anonymous regions (thread stacks, heap) can fail with ENOMEM ++ * when trying to lock uncommitted pages. This is expected - only ++ * committed pages get locked. Thread stacks MUST be locked to ++ * prevent swap-related deadlocks. ++ */ ++ if (errno == ENOMEM && strstr(line + pos, " 00:00 0")) { ++ log_debug_mem("mlock failed for anonymous region (uncommitted pages): %s", line); ++ return 1; ++ } + log_sys_error("mlock", line); + return 0; + } +-- +2.54.0 + diff --git a/0202-polldaemon-cleanup-bcache-before-background-poll-exi.patch b/0202-polldaemon-cleanup-bcache-before-background-poll-exi.patch new file mode 100644 index 0000000..a3ec398 --- /dev/null +++ b/0202-polldaemon-cleanup-bcache-before-background-poll-exi.patch @@ -0,0 +1,38 @@ +From 0422a2b75e62db3f0bfafd1e4511db8c0ddcc358 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 9 Apr 2026 21:47:51 +0200 +Subject: [PATCH 202/205] polldaemon: cleanup bcache before background poll + exit + +Add lvmcache_destroy and label_scan_destroy before _exit in +background polling child to close file descriptors to PV devices. + +Commit 7d71dac86 removed bcache cleanup from the poll loop to +avoid AIO context teardown overhead, but the background child +was still exiting via _exit without any cleanup, leaving file +descriptors open. This caused teardown failures with "Device +or resource busy" on slower systems (RHEL 7). + +Co-Authored-By: Claude Sonnet 4.5 +(cherry picked from commit e4f26d8ede2799e734bd8b1edc009dde5ac5b669) +--- + tools/polldaemon.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/tools/polldaemon.c b/tools/polldaemon.c +index f5d04311e..168f031cd 100644 +--- a/tools/polldaemon.c ++++ b/tools/polldaemon.c +@@ -685,7 +685,8 @@ static int _poll_daemon(struct cmd_context *cmd, struct poll_operation_id *id, + * because it will redundantly continue performing the + * caller's task (that the parent already performed) + */ +- /* FIXME Attempt proper cleanup */ ++ lvmcache_destroy(cmd, 1, 0); ++ label_scan_destroy(cmd); + _exit(lvm_return_code(ret)); + } + +-- +2.54.0 + diff --git a/0203-text_label-validate-pv_header-offset-to-prevent-out-.patch b/0203-text_label-validate-pv_header-offset-to-prevent-out-.patch new file mode 100644 index 0000000..97d9407 --- /dev/null +++ b/0203-text_label-validate-pv_header-offset-to-prevent-out-.patch @@ -0,0 +1,51 @@ +From 0d62fc2f1719c398e791c371cae94e01344fba79 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Fri, 10 Apr 2026 10:36:06 +0200 +Subject: [PATCH 203/205] text_label: validate pv_header offset to prevent + out-of-bounds read + +Add bounds checking for offset_xl before using it as a pointer offset +into the label buffer. Without validation, an attacker can set offset_xl +to an arbitrary 32-bit value causing pvhdr to point past the end of the +512-byte label buffer. + +The code immediately dereferences pvhdr to read pv_uuid, device_size_xl, +and iterate the disk_areas_xl array - all from out-of-bounds stack memory. + +Reported-by: Tony Asleson +Co-Authored-By: Claude Sonnet 4.5 +(cherry picked from commit 9edab8d87a2298d9e78e929cc74e8fab794b016f) +--- + lib/format_text/text_label.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/lib/format_text/text_label.c b/lib/format_text/text_label.c +index 59e1c268f..d68fe8804 100644 +--- a/lib/format_text/text_label.c ++++ b/lib/format_text/text_label.c +@@ -421,6 +421,7 @@ static int _text_read(struct cmd_context *cmd, struct labeller *labeller, struct + struct metadata_area *mda2 = NULL; + struct disk_locn *dlocn_xl; + uint64_t offset; ++ uint32_t pv_offset; + uint32_t ext_version; + uint32_t bad_fields; + int mda_count = 0; +@@ -431,7 +432,13 @@ static int _text_read(struct cmd_context *cmd, struct labeller *labeller, struct + /* + * PV header base + */ +- pvhdr = (struct pv_header *) ((char *) label_buf + xlate32(lh->offset_xl)); ++ pv_offset = xlate32(lh->offset_xl); ++ if (pv_offset > LABEL_SIZE - sizeof(struct pv_header)) { ++ log_error("Invalid pv_header offset %u on %s.", ++ pv_offset, dev_name(dev)); ++ return 0; ++ } ++ pvhdr = (struct pv_header *) ((char *) label_buf + pv_offset); + + memcpy(pvid, &pvhdr->pv_uuid, ID_LEN); + strncpy(vgid, FMT_TEXT_ORPHAN_VG_NAME, ID_LEN); +-- +2.54.0 + diff --git a/0204-libdaemon-harden-I-O-and-fix-crash-paths.patch b/0204-libdaemon-harden-I-O-and-fix-crash-paths.patch new file mode 100644 index 0000000..092effc --- /dev/null +++ b/0204-libdaemon-harden-I-O-and-fix-crash-paths.patch @@ -0,0 +1,157 @@ +From 8357e30d9e50d08c6a0fa6ba4cc93b7b2f2450b4 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 16 Apr 2026 01:36:02 +0200 +Subject: [PATCH 204/205] libdaemon: harden I/O and fix crash paths + +- Replace select() with poll() in buffer_read/buffer_write to avoid + stack buffer overflow when fd >= FD_SETSIZE (1024). +- Add DAEMON_MAX_MSG_SIZE (16 MiB) limit to buffer_read to prevent + unbounded allocation from malicious or buggy peer. +- Add NULL check in daemon_reply_int() and daemon_reply_str() to + prevent crash when response config tree is not available, matching + the server-side daemon_request_int/str pattern. +- Fix NULL dereference on res.cft->root in _client_thread when + handler returns neither buffer nor config tree. +- Fix res.cft leak on serialization failure in _client_thread. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 4ede3345aca466b5aa0541be6483a5ac58f7b7b9) +--- + libdaemon/client/daemon-client.h | 4 ++++ + libdaemon/client/daemon-io.c | 23 +++++++++++++++-------- + libdaemon/server/daemon-server.c | 14 +++++++++----- + 3 files changed, 28 insertions(+), 13 deletions(-) + +diff --git a/libdaemon/client/daemon-client.h b/libdaemon/client/daemon-client.h +index e1671a792..f6763dce5 100644 +--- a/libdaemon/client/daemon-client.h ++++ b/libdaemon/client/daemon-client.h +@@ -98,11 +98,15 @@ void daemon_reply_destroy(daemon_reply r); + + static inline int64_t daemon_reply_int(daemon_reply r, const char *path, int64_t def) + { ++ if (!r.cft) ++ return def; + return dm_config_find_int64(r.cft->root, path, def); + } + + static inline const char *daemon_reply_str(daemon_reply r, const char *path, const char *def) + { ++ if (!r.cft) ++ return def; + return dm_config_find_str_allow_empty(r.cft->root, path, def); + } + +diff --git a/libdaemon/client/daemon-io.c b/libdaemon/client/daemon-io.c +index 6b7437ecc..e26dc1dec 100644 +--- a/libdaemon/client/daemon-io.c ++++ b/libdaemon/client/daemon-io.c +@@ -17,6 +17,13 @@ + #include "daemon-io.h" + + #include ++/* ++ * #include ++ */ ++#include ++ ++/* Maximum incoming message size (16 MiB) to prevent unbounded allocation */ ++#define DAEMON_MAX_MSG_SIZE (16 * 1024 * 1024) + + /* + * Read a single message from a (socket) filedescriptor. Messages are delimited +@@ -27,6 +34,7 @@ + * See also write_buffer about blocking (read_buffer has identical behaviour). + */ + int buffer_read(int fd, struct buffer *buffer) { ++ struct pollfd pfd = { .fd = fd, .events = POLLIN }; + int result; + + if (!buffer_realloc(buffer, 32)) /* ensure we have some space */ +@@ -41,6 +49,10 @@ int buffer_read(int fd, struct buffer *buffer) { + buffer->mem[buffer->used] = 0; + break; /* success, we have the full message now */ + } ++ if (buffer->used > DAEMON_MAX_MSG_SIZE) { ++ errno = EOVERFLOW; ++ return 0; ++ } + if ((buffer->allocated - buffer->used < 32) && + !buffer_realloc(buffer, 1024)) + return 0; +@@ -49,11 +61,8 @@ int buffer_read(int fd, struct buffer *buffer) { + return 0; /* we should never encounter EOF here */ + } else if (result < 0 && (errno == EAGAIN || + errno == EINTR || errno == EIO)) { +- fd_set in; +- FD_ZERO(&in); +- FD_SET(fd, &in); + /* ignore the result, this is just a glorified sleep */ +- select(FD_SETSIZE, &in, NULL, NULL, NULL); ++ poll(&pfd, 1, -1); + } else if (result < 0) + return 0; + } +@@ -67,6 +76,7 @@ int buffer_read(int fd, struct buffer *buffer) { + */ + int buffer_write(int fd, const struct buffer *buffer) { + static const struct buffer _terminate = { .mem = (char *) "\n##\n", .used = 4 }; ++ struct pollfd pfd = { .fd = fd, .events = POLLOUT }; + const struct buffer *use; + int done, written, result; + +@@ -78,11 +88,8 @@ int buffer_write(int fd, const struct buffer *buffer) { + written += result; + else if (result < 0 && (errno == EAGAIN || + errno == EINTR || errno == EIO)) { +- fd_set out; +- FD_ZERO(&out); +- FD_SET(fd, &out); + /* ignore the result, this is just a glorified sleep */ +- select(FD_SETSIZE, NULL, &out, NULL, NULL); ++ poll(&pfd, 1, -1); + } else if (result < 0) + return 0; /* too bad */ + } +diff --git a/libdaemon/server/daemon-server.c b/libdaemon/server/daemon-server.c +index bb52857ce..76d3506cc 100644 +--- a/libdaemon/server/daemon-server.c ++++ b/libdaemon/server/daemon-server.c +@@ -443,6 +443,7 @@ static void *_client_thread(void *state) + thread_state *ts = state; + request req; + response res; ++ int r = 1; + + buffer_init(&req.buffer); + +@@ -462,11 +463,9 @@ static void *_client_thread(void *state) + if (res.error == EPROTO) /* Not a builtin, delegate to the custom handler. */ + res = ts->s.handler(ts->s, ts->client, req); + +- if (!res.buffer.mem) { +- if (!dm_config_write_node(res.cft->root, buffer_line, &res.buffer)) +- goto fail; +- if (!buffer_append(&res.buffer, "\n\n")) +- goto fail; ++ if (!res.buffer.mem && res.cft) { ++ r = dm_config_write_node(res.cft->root, buffer_line, &res.buffer) && ++ buffer_append(&res.buffer, "\n\n"); + dm_config_destroy(res.cft); + } + +@@ -474,6 +473,11 @@ static void *_client_thread(void *state) + dm_config_destroy(req.cft); + buffer_destroy(&req.buffer); + ++ if (!r) { ++ buffer_destroy(&res.buffer); ++ goto fail; ++ } ++ + daemon_log_multi(ts->s.log, DAEMON_LOG_WIRE, "-> ", res.buffer.mem); + buffer_write(ts->client.socket_fd, &res.buffer); + +-- +2.54.0 + diff --git a/0205-tools-add-arg_force_value-for-enum-handling.patch b/0205-tools-add-arg_force_value-for-enum-handling.patch new file mode 100644 index 0000000..248ef85 --- /dev/null +++ b/0205-tools-add-arg_force_value-for-enum-handling.patch @@ -0,0 +1,114 @@ +From 02d2b82426a82c6b36da69696914df84fd4d8254 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 10 Jul 2025 19:09:22 +0200 +Subject: [PATCH 205/205] tools: add arg_force_value() for enum handling + +Add arg_force_value() function that returns the correct force_t enum +type, replacing direct string comparisons. Update lvconvert and +pvremove to use this new function for better type safety. + +This is cleaner solution over just plain cast to force_t as we can +validate force level in use. + +(cherry picked from commit adb8ca503b389da6d93ed6a1fc8a343bd56457fb) +--- + tools/lvconvert.c | 8 ++++---- + tools/lvmcmdline.c | 15 +++++++++++++++ + tools/pvremove.c | 2 +- + tools/tools.h | 1 + + 4 files changed, 21 insertions(+), 5 deletions(-) + +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index 0b3b4bad8..eb62c3ff4 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -3117,7 +3117,7 @@ static int _lvconvert_to_pool(struct cmd_context *cmd, + .activate = CHANGE_AN, + .do_zero = 1, + .do_wipe_signatures = _get_wipe_signatures(cmd), +- .force = arg_count(cmd, force_ARG), ++ .force = arg_force_value(cmd), + .yes = arg_count(cmd, yes_ARG), + }; + int is_active; +@@ -3384,7 +3384,7 @@ static int _lvconvert_to_pool(struct cmd_context *cmd, + .do_wipe_signatures = 1, + .is_metadata = 1, + .yes = arg_count(cmd, yes_ARG), +- .force = arg_count(cmd, force_ARG) } )) { ++ .force = arg_force_value(cmd) } )) { + log_error("Aborting. Failed to wipe metadata lv."); + goto bad; + } +@@ -5626,7 +5626,7 @@ static int _lvconvert_to_vdopool_single(struct cmd_context *cmd, + .do_zero = arg_int_value(cmd, zero_ARG, 1), + .do_wipe_signatures = _get_wipe_signatures(cmd), + .yes = arg_count(cmd, yes_ARG), +- .force = arg_count(cmd, force_ARG) ++ .force = arg_force_value(cmd) + }; + + if (vcp.lv_name) { +@@ -5996,7 +5996,7 @@ static int _writecache_zero(struct cmd_context *cmd, struct logical_volume *lv) + .do_wipe_signatures = 1, /* optional, to print warning if clobbering something */ + .do_zero = 1, /* required for dm-writecache to work */ + .yes = arg_count(cmd, yes_ARG), +- .force = arg_count(cmd, force_ARG) ++ .force = arg_force_value(cmd) + }; + int ret; + +diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c +index df11f57ab..b88b972a3 100644 +--- a/tools/lvmcmdline.c ++++ b/tools/lvmcmdline.c +@@ -379,6 +379,21 @@ percent_type_t arg_percent_value(const struct cmd_context *cmd, int a, const per + return arg_is_set(cmd, a) ? cmd->opt_arg_values[a].percent : def; + } + ++force_t arg_force_value(const struct cmd_context *cmd) ++{ ++ int f; ++ ++ switch ((f = arg_count(cmd, force_ARG))) { ++ case 0: return PROMPT; ++ case 1: return DONT_PROMPT; ++ default: ++ log_debug("Changing force level %d to %d.", ++ f, DONT_PROMPT_OVERRIDE); ++ /* fall through */ ++ case 2: return DONT_PROMPT_OVERRIDE; ++ } ++} ++ + int arg_count_increment(struct cmd_context *cmd, int a) + { + return cmd->opt_arg_values[a].count++; +diff --git a/tools/pvremove.c b/tools/pvremove.c +index 5c39ee0c7..4a69d50bb 100644 +--- a/tools/pvremove.c ++++ b/tools/pvremove.c +@@ -29,7 +29,7 @@ int pvremove(struct cmd_context *cmd, int argc, char **argv) + pvcreate_params_set_defaults(&pp); + + pp.is_remove = 1; +- pp.force = arg_count(cmd, force_ARG); ++ pp.force = arg_force_value(cmd); + pp.yes = arg_count(cmd, yes_ARG); + pp.pv_count = argc; + pp.pv_names = argv; +diff --git a/tools/tools.h b/tools/tools.h +index 8da653537..3e618912a 100644 +--- a/tools/tools.h ++++ b/tools/tools.h +@@ -148,6 +148,7 @@ const void *arg_ptr_value(const struct cmd_context *cmd, int a, const void *def) + sign_t arg_sign_value(const struct cmd_context *cmd, int a, const sign_t def); + percent_type_t arg_percent_value(const struct cmd_context *cmd, int a, const percent_type_t def); + int arg_count_increment(struct cmd_context *cmd, int a); ++force_t arg_force_value(const struct cmd_context *cmd); + + unsigned grouped_arg_count(const struct arg_values *av, int a); + unsigned grouped_arg_is_set(const struct arg_values *av, int a); +-- +2.54.0 + diff --git a/lvm2.spec b/lvm2.spec index 7e51263..1a8b693 100644 --- a/lvm2.spec +++ b/lvm2.spec @@ -55,7 +55,7 @@ Version: 2.03.33 %if 0%{?from_snapshot} Release: 0.1.20211115git%{shortcommit}%{?dist}%{?rel_suffix} %else -Release: 5%{?dist}%{?rel_suffix} +Release: 6%{?dist}%{?rel_suffix} %endif License: GPL-2.0-only URL: https://sourceware.org/lvm2 @@ -129,6 +129,151 @@ Patch59: 0059-cov-fix-leaking-fd.patch Patch60: 0060-lvmlockd-sanlock-fix-uninitialized-time-value.patch # RHEL-153394: Patch61: 0061-libdaemon-CLOEXEC-descriptors-are-not-stray-fds.patch +# Patches from upstream up to 2.03.41: +Patch62: 0062-cov-pvck-fix-TOCTOU-race-in-get_devicefile.patch +Patch63: 0063-cov-libdm-fix-TOCTOU-race-in-_sysfs_find_kernel_name.patch +Patch64: 0064-cov-dev-mpath-fix-TOCTOU-race-in-holders-directory-c.patch +Patch65: 0065-cov-dev-cache-fix-TOCTOU-race-in-devices_file_rename.patch +Patch66: 0066-libdm-dbg_malloc-fix-buffer-overflow-in-dm_realloc_a.patch +Patch67: 0067-libdevmapper-event-fix-read-buffer-overflow-in-_daem.patch +Patch68: 0068-dmsetup-fix-dangling-pointer-in-_slurp_stdin-after-r.patch +Patch69: 0069-export-fix-out-of-bounds-access-in-_sectors_to_units.patch +Patch70: 0070-id-fix-out-of-bounds-read-in-_id_valid-with-corrupt-.patch +Patch71: 0071-clang-lvmcache-fix-use-after-free-in-lvmcache_update.patch +Patch72: 0072-clang-lvmlockd-fix-use-after-free-in-res_process.patch +Patch73: 0073-cov-device_id-fix-NULL-dereference-in-_dev_has_id.patch +Patch74: 0074-dmeventd-snapshot-fix-NULL-target_type-passed-to-log.patch +Patch75: 0075-libdaemon-config-util-fix-NULL-dereference-in-error-.patch +Patch76: 0076-clang-metadata-add-NULL-check-for-pva-in-_alloc_para.patch +Patch77: 0077-clang-libdm-fix-dangling-parent-pointer-to-stack-var.patch +Patch78: 0078-parse_vpd-fix-VPD-descriptor-bounds-checking.patch +Patch79: 0079-cov-dev-ext-validate-src-bounds-in-dev_ext_enable.patch +Patch80: 0080-cov-config-check-for-size-overflow-in-config_file_re.patch +Patch81: 0081-cov-format_text-validate-rlocn-bounds-before-uint32_.patch +Patch82: 0082-label-clear-DEV_IN_BCACHE-on-bcache_set_fd-failure.patch +Patch83: 0083-lv_manip-fix-copy-paste-error-in-thin-pool-metadata-.patch +Patch84: 0084-activate-fix-cachevol-cmeta-cdata-device-offsets.patch +Patch85: 0085-merge-fix-wrong-variable-in-mirror-size-check-log_er.patch +Patch86: 0086-dev_manager-fix-wrong-sizeof-in-raid-status-allocati.patch +Patch87: 0087-cov-thin-fix-wrong-variable-in-chunk_size-error-mess.patch +Patch88: 0088-vdo_manip-fix-missing-si.mem_unit-in-total-memory-ca.patch +Patch89: 0089-dmeventd-vdo-fix-name-overwrite-and-missing-max_fail.patch +Patch90: 0090-lvmlockd-dlm-fix-wrong-variable-in-fopen-and-error-l.patch +Patch91: 0091-lvmlockd-sanlock-fix-wrong-variable-in-error-log.patch +Patch92: 0092-daemon-client-fix-swapped-parameters-in-log_debug.patch +Patch93: 0093-raid_manip-fix-swapped-parameters-in-error-message.patch +Patch94: 0094-cov-memlock-fix-open-return-value-check.patch +Patch95: 0095-filesystem-fix-nofs-early-return-checking-wrong-stru.patch +Patch96: 0096-lvconvert-fix-inverted-proc_dir-check-and-wrong-size.patch +Patch97: 0097-lvmlockd-idm-fix-stale-rv-check-and-missing-in-conve.patch +Patch98: 0098-cov-lv-fix-off-by-one-in-_sublvs_remove_after_reshap.patch +Patch99: 0099-hints-fix-off-by-one-in-orphan-vgname-check.patch +Patch100: 0100-lvmpolld-fix-comma-typo-and-off-by-one-in-timeout-ch.patch +Patch101: 0101-pvmove_poll-fix-off-by-one-in-mirror-image-bounds-ch.patch +Patch102: 0102-pvck-fix-copy-paste-error-checking-mda2_size_set.patch +Patch103: 0103-online-fix-buffer-pointer-in-write-loop.patch +Patch104: 0104-libdm-fix-cut-and-past-bug.patch +Patch105: 0105-lvmpolld-fix-STDOUT-STDERR-copy-paste-error-in-poll-.patch +Patch106: 0106-lvmcache-use-label_destroy-instead-of-free-for-label.patch +Patch107: 0107-lvm-null-correct-field-after-freeing-log_rh.patch +Patch108: 0108-dmeventd-fix-msg.data-memory-leak-in-_restart_dmeven.patch +Patch109: 0109-polldaemon-check-finish_copy-result-on-abort-path.patch +Patch110: 0110-metadata-fix-lv_raid_healthy-to-detect-metadata-alra.patch +Patch111: 0111-lv_manip-detect-insufficient-space-for-RAID-with-spl.patch +Patch112: 0112-libdm-deptree-cancel-delay_resume-for-new-children-w.patch +Patch113: 0113-integrity-fix-adding-integrity-to-RAID.patch +Patch114: 0114-integrity-fix-activation-race-during-setup.patch +Patch115: 0115-snapshot-reject-lvreduce-that-would-truncate-COW-exc.patch +Patch116: 0116-snapshot-reject-resize-of-merging-CoW-snapshot.patch +Patch117: 0117-lvresize-skip-fs-handling-for-CoW-snapshot-COW-store.patch +Patch118: 0118-metadata-fix-vg_split_mdas-losing-first-_move_mdas-r.patch +Patch119: 0119-libdm-stats-fix-bitwise-AND-and-use-escaped-aux_data.patch +Patch120: 0120-libdm-report-fix-missing-negation-in-time-range-not-.patch +Patch121: 0121-libdm-report-fix-row-sorting-of-percent-fields.patch +Patch122: 0122-libdm-fix-row-sorting-for-string-list-fields.patch +Patch123: 0123-libdm-report-fix-row-sorting-for-string-list-fields-.patch +Patch124: 0124-device_id-fix-checking-wrong-buffer-after-format_gen.patch +Patch125: 0125-device_id-prevent-incorrect-dm-uuid-idtype.patch +Patch126: 0126-lvmcmdline-fix-line-too-long-check-using-wrong-buffe.patch +Patch127: 0127-report-fix-lvseg_metadata_devices_str-calling-wrong-.patch +Patch128: 0128-report-fix-lvseg_seg_le_ranges_str-calling-wrong-fun.patch +Patch129: 0129-vgchange-add-NULL-check-for-vg-system_id-before-strc.patch +Patch130: 0130-libdm-iface-use-st_rdev-instead-of-st_mode-for-MAJOR.patch +Patch131: 0131-lvmcache-fix-unnecessary-address-of-on-array-in-memc.patch +Patch132: 0132-RHEL9-fix-compilation-issues.patch +Patch133: 0133-cov-command-fix-NULL-dereference-in-_add_oo_definiti.patch +Patch134: 0134-bcache-fix-flush-orphaning-locked-dirty-blocks.patch +Patch135: 0135-dmeventd-fix-inverted-systemd-activation-env-var-che.patch +Patch136: 0136-lvrename-fix-historical-LV-prefix-stripping-using-wr.patch +Patch137: 0137-lvmdevices-fix-resource-leak-on-delpvid-duplicate-er.patch +Patch138: 0138-dev-cache-fix-missing-dev_iter_destroy-in-iterate_de.patch +Patch139: 0139-import_vsn1-fix-resource-leak-on-historical-LV-id-re.patch +Patch140: 0140-lvm-exec-fix-close-error-msg-and-null_fd-fd-case.patch +Patch141: 0141-lvmlockctl-fix-close-error-msg-and-null_fd-fd-case.patch +Patch142: 0142-bcache-do-not-call-io_destroy-in-forked-process.patch +Patch143: 0143-debug-add-tracing-for-context-destroy.patch +Patch144: 0144-bcache-report-libaio-errors-properly.patch +Patch145: 0145-bcache-retry-io_getevents-on-EINTR.patch +Patch146: 0146-bcache-retry-io_getevents-only-if-sigint-not-caught.patch +Patch147: 0147-bcache-fix-_wait_io-failure-ignored-by-_wait_all-and.patch +Patch148: 0148-libdm-fix-cache-feature_flags-cleaner-policy-to-pres.patch +Patch149: 0149-libdm-fix-cache-origin-uuid-error-message-printing-w.patch +Patch150: 0150-lvmlockd-check-if-sanlock-lv-is-not-partial-before-t.patch +Patch151: 0151-pool-initialize-chunk_size_calc_method-in-update-fun.patch +Patch152: 0152-pool-drop-LV_ACTIVATION_SKIP-from-converted-LVs.patch +Patch153: 0153-lib-fix-buffer-overflows-in-device_id-and-GPT-parsin.patch +Patch154: 0154-lib-add-missing-NULL-checks-to-prevent-dereference-c.patch +Patch155: 0155-pvck-fix-buffer-overflow-integer-truncation-and-type.patch +Patch156: 0156-lvmpolld-replace-asserts-with-proper-error-handling.patch +Patch157: 0157-import_vsn1-validate-area_count-before-subtracting-p.patch +Patch158: 0158-format-text-validate-mda-size-and-rlocn-size-limits.patch +Patch159: 0159-lv_manip-validate-area_count-against-MAX_STRIPES.patch +Patch160: 0160-lvmcache-fix-use-after-free-on-vgid-update-failure.patch +Patch161: 0161-config-fix-strtoll-error-checking.patch +Patch162: 0162-libdm-config-add-missing-NULL-check-for-n-v-in-_find.patch +Patch163: 0163-polldaemon-add-missing-NULL-check-for-display_name-w.patch +Patch164: 0164-toollib-fix-memory-leak-in-process_each_label-duplic.patch +Patch165: 0165-lvmpolld-fix-pthread_attr-leak-in-spawn_detached_thr.patch +Patch166: 0166-vgimportclone-fix-VG-lock-leak-on-second-lock_vol-fa.patch +Patch167: 0167-pvscan-fix-VG-lock-leak-on-early-return-in-_pvscan_a.patch +Patch168: 0168-cache-fix-cache_check_for_warns-reading-wrong-cache-.patch +Patch169: 0169-metadata-fix-vg_is_foreign-missing-NULL-and-empty-sy.patch +Patch170: 0170-lvchange-fix-swapped-printf-args-and-wrong-arg_long_.patch +Patch171: 0171-pvchange-use-arg_force_value-instead-of-raw-arg_coun.patch +Patch172: 0172-raid-fix-missing-failure-return-after-reshape-space-.patch +Patch173: 0173-integrity-fail-on-lv_remove-error-in-lv_remove_integ.patch +Patch174: 0174-libdm-preserve-format_flags-in-_clone_config_value.patch +Patch175: 0175-lv-check-dm_pool_strdup-return-in-lv_set_creation.patch +Patch176: 0176-libdm-return-error-for-unknown-value-type-in-_write_.patch +Patch177: 0177-libdm-propagate-return-value-from-recursive-dm_tree_.patch +Patch178: 0178-libdm-add-missing-SEG_RAID10-case-in-dm_tree_node_ad.patch +Patch179: 0179-libdm-fix-undersized-group_str-buffer-in-dm_stats_st.patch +Patch180: 0180-libdm-decode-DM_STATS_WALK_GROUP-flag-before-using-g.patch +Patch181: 0181-libdm-fix-dm_pool_strndup-over-allocation.patch +Patch182: 0182-dmeventd-reject-zero-length-daemon-response.patch +Patch183: 0183-dmeventd-treat-ENODEV-same-as-ENXIO-in-event-wait.patch +Patch184: 0184-dmfilemapd-fix-fd-validity-check-from-0-to-0.patch +Patch185: 0185-device-fix-fd-validity-check-to-accept-fd-0.patch +Patch186: 0186-lvmlockd-fix-off-by-one-in-match_dm_uuid-lv-uuid-ext.patch +Patch187: 0187-lvmlockd-fix-lockspace-use-after-mutex-unlock-in-wor.patch +Patch188: 0188-lvmlockd-fix-off-by-one-in-_owner_str-snprintf-size.patch +Patch189: 0189-lvmlockd-fix-null-pointer-arithmetic-in-read_host_id.patch +Patch190: 0190-lvmlockd-fix-_free_vg-ignoring-error-result.patch +Patch191: 0191-lvmlockd-use-CLOCK_MONOTONIC-for-pthread_cond_timedw.patch +Patch192: 0192-lvconvert-fix-dm_strncpy-off-by-one-in-_raid_split_i.patch +Patch193: 0193-lvcreate-fix-recovery-rate-check-when-max-is-unset.patch +Patch194: 0194-lvconvert-add-missing-archive-in-_cache_vol_attach.patch +Patch195: 0195-toolcontext-fix-_set_time_format-to-advance-past-for.patch +Patch196: 0196-integrity-pass-vg-extent_size-in-lv_extend_integrity.patch +Patch197: 0197-pool-fix-fragile-_pmspare-suffix-stripping.patch +Patch198: 0198-toolcontext-fix-init_run_by_dmeventd-to-return-succe.patch +Patch199: 0199-label-fix-_in_bcache-returning-NULL-instead-of-false.patch +Patch200: 0200-mirror-match-alloca-size-with-memset-in-_form_mirror.patch +Patch201: 0201-memlock-handle-ENOMEM-gracefully-for-anonymous-regio.patch +Patch202: 0202-polldaemon-cleanup-bcache-before-background-poll-exi.patch +Patch203: 0203-text_label-validate-pv_header-offset-to-prevent-out-.patch +Patch204: 0204-libdaemon-harden-I-O-and-fix-crash-paths.patch +Patch205: 0205-tools-add-arg_force_value-for-enum-handling.patch BuildRequires: make BuildRequires: gcc @@ -763,6 +908,9 @@ An extensive functional testsuite for LVM2. %endif %changelog +* Thu Jun 04 2026 Marian Csontos - 2.03.33-6.el9 +- Bring important patches from upstream up to verison 2.03.41. + * Thu May 07 2026 Marian Csontos - 2.03.33-5.el9 - Fix false positive warnings about stray FDs on s390x.