Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package s390-tools for openSUSE:Factory checked in at 2026-08-18 16:38:09 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/s390-tools (Old) and /work/SRC/openSUSE:Factory/.s390-tools.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "s390-tools" Tue Aug 18 16:38:09 2026 rev:123 rq:1371751 version:2.44.0 Changes: -------- --- /work/SRC/openSUSE:Factory/s390-tools/s390-tools.changes 2026-08-06 16:28:51.410353918 +0200 +++ /work/SRC/openSUSE:Factory/.s390-tools.new.1258/s390-tools.changes 2026-08-18 16:38:54.345132375 +0200 @@ -1,0 +2,13 @@ +Tue Aug 18 10:50:16 UTC 2026 - Nikolay Gueorguiev <[email protected]> + +- Applied patches (bsc#1275475, bsc#1275476) + Fixes for Security vulnerabilities for zkey and friends + * s390-tools-zkey-Harden-KMS-config-directory-handling.patch + - zkey: Harden KMS config directory handling - Commit `0eef784` + * s390-tools-zkey-Harden-zkey-kms-unbind-command.patch + - zkey: Harden 'zkey kms unbind' command - Commit `1b90d15` + * s390-tools-zkey-ekmfweb-zkey-kmip-Harden-KMS-plugin-file-handling.patch + - zkey/ekmfweb, zkey/kmip: Harden KMS plugin file handling - Commit `bc81c32` +- Re-vendor-ed vendor.tar.zst + +------------------------------------------------------------------- New: ---- s390-tools-zkey-Harden-KMS-config-directory-handling.patch s390-tools-zkey-Harden-zkey-kms-unbind-command.patch s390-tools-zkey-ekmfweb-zkey-kmip-Harden-KMS-plugin-file-handling.patch ----------(New B)---------- New: Fixes for Security vulnerabilities for zkey and friends * s390-tools-zkey-Harden-KMS-config-directory-handling.patch - zkey: Harden KMS config directory handling - Commit `0eef784` New: - zkey: Harden KMS config directory handling - Commit `0eef784` * s390-tools-zkey-Harden-zkey-kms-unbind-command.patch - zkey: Harden 'zkey kms unbind' command - Commit `1b90d15` New: - zkey: Harden 'zkey kms unbind' command - Commit `1b90d15` * s390-tools-zkey-ekmfweb-zkey-kmip-Harden-KMS-plugin-file-handling.patch - zkey/ekmfweb, zkey/kmip: Harden KMS plugin file handling - Commit `bc81c32` ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ s390-tools.spec ++++++ --- /var/tmp/diff_new_pack.XFCnAJ/_old 2026-08-18 16:38:55.866186807 +0200 +++ /var/tmp/diff_new_pack.XFCnAJ/_new 2026-08-18 16:38:55.869186914 +0200 @@ -92,6 +92,9 @@ ### # IBM patches +Patch101: s390-tools-zkey-Harden-KMS-config-directory-handling.patch +Patch102: s390-tools-zkey-Harden-zkey-kms-unbind-command.patch +Patch103: s390-tools-zkey-ekmfweb-zkey-kmip-Harden-KMS-plugin-file-handling.patch ### # SUSE patches Patch900: s390-tools-combined.patch ++++++ s390-tools-zkey-Harden-KMS-config-directory-handling.patch ++++++ >From 0eef7846bd9d29546877edd1368b5b0d7162132a Mon Sep 17 00:00:00 2001 From: Ingo Franzki <[email protected]> Date: Fri, 31 Jul 2026 10:48:35 +0200 Subject: [PATCH] zkey: Harden KMS config directory handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the zkey repository is bound to a KMS, a KMS config directory is created inside the zkey repository. The path of this config directory is stored in the kms.conf properties file. The properties file could be manipulated by a malicious user (which must be a member of the 'zkeyadm' user group) and the config directory path could be changed to point to a path outside of the zkey repository. Whenever using the config directory, make sure that it is inside the zkey repository and fail early if not. Signed-off-by: Ingo Franzki <[email protected]> Reviewed-by: Finn Callies <[email protected]> Signed-off-by: Jan Höppner <[email protected]> --- zkey/kms.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++- zkey/kms.h | 3 ++- zkey/zkey.c | 2 +- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/zkey/kms.c b/zkey/kms.c index 548ffba6..464f5293 100644 --- a/zkey/kms.c +++ b/zkey/kms.c @@ -479,15 +479,54 @@ int check_for_kms_plugin(struct kms_info *kms_info, bool verbose) return rc; } +/* + * Returns true if the specified path is within the base path. + * It canonicalizes both, the base path and the path, and compares that + * the path starts with the base path followed by a '/'. + */ +static bool is_path_within_base_path(const char *base_path, const char *path) +{ + char *real_base_path, *real_path; + size_t base_len; + bool ret = false; + + real_base_path = canonicalize_file_name(base_path); + real_path = canonicalize_file_name(path); + + if (real_base_path == NULL || real_path == NULL) { + warnx("Failed to resolve path for safety check: %s", + strerror(errno)); + goto out; + } + + /* Require path to be strictly inside the base path */ + base_len = strlen(real_base_path); + if (strncmp(real_path, real_base_path, base_len) != 0 || + real_path[base_len] != '/') { + warnx("'%s' is not inside '%s'", path, base_path); + goto out; + } + + ret = true; + +out: + free(real_base_path); + free(real_path); + + return ret; +} + /** * Initializes the KMS plugin. * * @param[in] kms_info The KMS Plugin info + * @param[in] keystore the keystore to bind to the plugin * @param[in] verbose if true, verbose messages are printed * * @returns 0 for success or a negative errno in case of an error. */ -int init_kms_plugin(struct kms_info *kms_info, bool verbose) +int init_kms_plugin(struct kms_info *kms_info, struct keystore *keystore, + bool verbose) { char *config_path = NULL; char **apqn_list = NULL; @@ -495,6 +534,7 @@ int init_kms_plugin(struct kms_info *kms_info, bool verbose) int i, rc = 0; util_assert(kms_info != NULL, "Internal error: kms_info is NULL"); + util_assert(keystore != NULL, "Internal error: keystore is NULL"); config_path = properties_get(kms_info->props, KMS_CONFIG_PROP_KMS_CONFIG); @@ -504,6 +544,12 @@ int init_kms_plugin(struct kms_info *kms_info, bool verbose) goto out; } + if (!is_path_within_base_path(keystore->directory, config_path)) { + warnx("Invalid KMS config directory: '%s'", config_path); + rc = -EIO; + goto out; + } + if (kms_info->funcs->kms_initialize != NULL) { kms_info->handle = kms_info->funcs->kms_initialize(config_path, verbose); diff --git a/zkey/kms.h b/zkey/kms.h index 336e935f..7bed94a5 100644 --- a/zkey/kms.h +++ b/zkey/kms.h @@ -33,7 +33,8 @@ int list_kms_plugins(bool verbose); int check_for_kms_plugin(struct kms_info *kms_info, bool verbose); -int init_kms_plugin(struct kms_info *kms_info, bool verbose); +int init_kms_plugin(struct kms_info *kms_info, struct keystore *keystore, + bool verbose); void free_kms_plugin(struct kms_info *kms_info); diff --git a/zkey/zkey.c b/zkey/zkey.c index a2b94789..f82e7e64 100644 --- a/zkey/zkey.c +++ b/zkey/zkey.c @@ -3798,7 +3798,7 @@ int main(int argc, char *argv[]) } if (g.kms_info.plugin_lib != NULL) { - rc = init_kms_plugin(&g.kms_info, g.verbose); + rc = init_kms_plugin(&g.kms_info, g.keystore, g.verbose); if (rc != 0) { rc = EXIT_FAILURE; goto out; ++++++ s390-tools-zkey-Harden-zkey-kms-unbind-command.patch ++++++ >From 1b90d15f1057c02bf581707253294d2d2bd7a8c4 Mon Sep 17 00:00:00 2001 From: Ingo Franzki <[email protected]> Date: Mon, 3 Aug 2026 10:08:19 +0200 Subject: [PATCH] zkey: Harden 'zkey kms unbind' command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'zkey kms unbind' command removes the KMS config directory and all files contained. Make sure that when removing the KMS config directory that it is inside the zkey repository to not follow symlinks when the KMS config directory itself or any files or directories inside that directory are symlinks. Open the directory with O_NOFOLLOW to protect from symlink traversal. Symlinks to files or directories within the directory are detected via fstatat() with AT_SYMLINK_NOFOLLOW and cause the removal to fail. Signed-off-by: Ingo Franzki <[email protected]> Reviewed-by: Finn Callies <[email protected]> Signed-off-by: Jan Höppner <[email protected]> --- zkey/kms.c | 85 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/zkey/kms.c b/zkey/kms.c index 464f5293..3f2e8d64 100644 --- a/zkey/kms.c +++ b/zkey/kms.c @@ -772,35 +772,48 @@ int bind_kms_plugin(struct keystore *keystore, const char *plugin, /** * Removes a directory and all its contents. */ -static int remove_directory_recursively(const char *directory) +static int remove_directory_recursively(int parent_fd, const char *name) { - char *filename = NULL; struct dirent *de; + struct stat st; + int rc = 0, dirfd; DIR *dirp; - int rc = 0; - dirp = opendir(directory); + dirfd = openat(parent_fd, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (dirfd < 0) { + rc = -errno; + warnx("Failed to open directory '%s': %s", name, strerror(-rc)); + return rc; + } + + dirp = fdopendir(dirfd); if (dirp == NULL) { rc = -errno; - warnx("Failed to open directory '%s'", directory); + close(dirfd); + warnx("Failed to open directory '%s': %s", name, strerror(-rc)); return rc; } while ((de = readdir(dirp))) { - util_asprintf(&filename, "%s/%s", directory, de->d_name); - if (de->d_type == DT_DIR) { - if (strcmp(de->d_name, ".") != 0 && - strcmp(de->d_name, "..") != 0) - rc = remove_directory_recursively(filename); + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + + if (fstatat(dirfd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0) { + rc = -errno; + warnx("Failed to stat '%s': %s", de->d_name, + strerror(-rc)); + break; + } + + if (S_ISDIR(st.st_mode)) { + rc = remove_directory_recursively(dirfd, de->d_name); } else { - rc = remove(filename); - if (rc != 0) { + if (unlinkat(dirfd, de->d_name, 0) != 0) { rc = -errno; - warnx("Failed to remove '%s': %s", filename, + warnx("Failed to remove '%s': %s", de->d_name, strerror(-rc)); } } - free(filename); if (rc != 0) break; @@ -809,9 +822,9 @@ static int remove_directory_recursively(const char *directory) if (rc != 0) goto out; - if (rmdir(directory) != 0) { + if (unlinkat(parent_fd, name, AT_REMOVEDIR) != 0) { rc = -errno; - warnx("Failed to remove '%s': %s", directory, strerror(-rc)); + warnx("Failed to remove '%s': %s", name, strerror(-rc)); goto out; } @@ -835,9 +848,7 @@ static int remove_directory_recursively(const char *directory) int unbind_kms_plugin(struct kms_info *kms_info, struct keystore *keystore, bool UNUSED(verbose)) { - char *config_dir = NULL; - char *filename = NULL; - int rc; + int rc, dirfd = -1; util_assert(kms_info != NULL, "Internal error: kms_info is NULL"); util_assert(keystore != NULL, "Internal error: keystore is NULL"); @@ -860,30 +871,34 @@ int unbind_kms_plugin(struct kms_info *kms_info, struct keystore *keystore, } } - config_dir = properties_get(kms_info->props, - KMS_CONFIG_PROP_KMS_CONFIG); - if (config_dir != NULL) { - rc = remove_directory_recursively(config_dir); - if (rc != 0) { - warnx("Failed to remove the KMS plugin's config " - "directory: %s", strerror(-rc)); - goto out; - } + dirfd = open(keystore->directory, + O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (dirfd < 0) { + rc = -errno; + warnx("Failed to open directory '%s': %s", + keystore->directory, strerror(-rc)); + goto out; } - util_asprintf(&filename, "%s/%s", keystore->directory, KMS_CONFIG_FILE); - rc = remove(filename); + rc = remove_directory_recursively(dirfd, kms_info->plugin_name); if (rc != 0) { + warnx("Failed to remove the KMS plugin's config " + "directory '%s/%s': %s", keystore->directory, + kms_info->plugin_name, strerror(-rc)); + goto out; + } + + if (unlinkat(dirfd, KMS_CONFIG_FILE, 0) != 0) { rc = -errno; - warnx("Failed to remove '%s': %s", filename, strerror(-rc)); + warnx("Failed to remove '%s/%s': %s", keystore->directory, + KMS_CONFIG_FILE, strerror(-rc)); goto out; } out: - if (config_dir != NULL) - free(config_dir); - if (filename != NULL) - free(filename); + if (dirfd >= 0) + close(dirfd); + return rc; } ++++++ s390-tools-zkey-ekmfweb-zkey-kmip-Harden-KMS-plugin-file-handling.patch ++++++ >From bc81c3221872e465bbed29ba9092f822b514ae2c Mon Sep 17 00:00:00 2001 From: Ingo Franzki <[email protected]> Date: Mon, 3 Aug 2026 11:29:18 +0200 Subject: [PATCH] zkey/ekmfweb, zkey/kmip: Harden KMS plugin file handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the plugin file handling helpers to always operate relative to the plugin config directory and thus avoid following symlinks. Open the config directory at plugin initialization time (with O_DIRECTORY and O_NOFOLLOW) and then perform the file operations relative to that directory handle, i.e. via openat(..., O_NOFOLLOW), unlinkat(), and renameat(). For file paths read from a properties file, make sure that they are inside the plugin configuration directory and fail early if not. Signed-off-by: Ingo Franzki <[email protected]> Reviewed-by: Finn Callies <[email protected]> Signed-off-by: Jan Höppner <[email protected]> --- zkey/ekmfweb/zkey-ekmfweb.c | 35 +++++++----- zkey/kmip/zkey-kmip.c | 47 ++++++++++------ zkey/plugin-utils.c | 108 ++++++++++++++++++++++++++++++++---- zkey/plugin-utils.h | 13 +++++ 4 files changed, 163 insertions(+), 40 deletions(-) diff --git a/zkey/ekmfweb/zkey-ekmfweb.c b/zkey/ekmfweb/zkey-ekmfweb.c index 6fa3a5ddd..0cbc332c9 100644 --- a/zkey/ekmfweb/zkey-ekmfweb.c +++ b/zkey/ekmfweb/zkey-ekmfweb.c @@ -218,6 +218,10 @@ static int _get_ekmf_config(struct plugin_handle *ph) ph->ekmf_config.identity_secure_key = properties_get(ph->pd.properties, EKMFWEB_CONFIG_IDENTITY_KEY); + if (ph->ekmf_config.identity_secure_key != NULL && + !plugin_path_is_within_config(&ph->pd, + ph->ekmf_config.identity_secure_key)) + return -EINVAL; ph->ekmf_config.base_url = properties_get(ph->pd.properties, EKMFWEB_CONFIG_URL); @@ -305,7 +309,7 @@ static void _remove_login_token_if_error(struct plugin_handle *ph, int error) switch (error) { case -EACCES: case -EPERM: - remove(ph->ekmf_config.login_token); + plugin_remove_config_file(&ph->pd, ph->ekmf_config.login_token); FREE_AND_SET_NULL(ph->ekmf_config.login_token); break; default: @@ -1712,7 +1716,7 @@ static int _get_ekmfweb_settings(struct plugin_handle *ph) _check_config_complete(ph); if (ph->ekmf_config.login_token != NULL) { - remove(ph->ekmf_config.login_token); + plugin_remove_config_file(&ph->pd, ph->ekmf_config.login_token); FREE_AND_SET_NULL(ph->ekmf_config.login_token); } rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_LOGIN_TOKEN, @@ -1743,7 +1747,8 @@ static int _get_ekmfweb_settings(struct plugin_handle *ph) goto out; if (ph->ekmf_config.ekmf_server_pubkey != NULL) - remove(ph->ekmf_config.ekmf_server_pubkey); + plugin_remove_config_file(&ph->pd, + ph->ekmf_config.ekmf_server_pubkey); FREE_AND_SET_NULL(ph->ekmf_config.ekmf_server_pubkey); util_asprintf((char **)&ph->ekmf_config.ekmf_server_pubkey, @@ -2048,7 +2053,7 @@ static int _configure_connection(struct plugin_handle *ph, if (rc != 0) goto out; } else { - remove(server_cert_file); + plugin_remove_config_file(&ph->pd, server_cert_file); } rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_SERVER_CERT, tls_trust_server_cert ? @@ -2067,7 +2072,7 @@ static int _configure_connection(struct plugin_handle *ph, if (rc != 0) goto out; } else { - remove(server_pubkey_file); + plugin_remove_config_file(&ph->pd, server_pubkey_file); } rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_SERVER_PUBKEY, @@ -2078,13 +2083,13 @@ static int _configure_connection(struct plugin_handle *ph, out: if (server_cert_temp != NULL) { - remove(server_cert_temp); + plugin_remove_config_file(&ph->pd, server_cert_temp); free(server_cert_temp); } if (server_cert_file != NULL) free(server_cert_file); if (server_pubkey_temp != NULL) { - remove(server_pubkey_temp); + plugin_remove_config_file(&ph->pd, server_pubkey_temp); free(server_pubkey_temp); } if (server_pubkey_file != NULL) @@ -2350,7 +2355,7 @@ static int _generate_identity_key(struct plugin_handle *ph) reenc_file = properties_get(ph->pd.properties, EKMFWEB_CONFIG_IDENTITY_KEY_REENC); if (reenc_file != NULL) { - remove(reenc_file); + plugin_remove_config_file(&ph->pd, reenc_file); free(reenc_file); properties_remove(ph->pd.properties, EKMFWEB_CONFIG_IDENTITY_KEY_REENC); @@ -3487,7 +3492,7 @@ int kms_login(const kms_handle_t handle) if (rc == 0 && valid) return 0; - remove(ph->ekmf_config.login_token); + plugin_remove_config_file(&ph->pd, ph->ekmf_config.login_token); FREE_AND_SET_NULL(ph->ekmf_config.login_token); rc = plugin_set_or_remove_property(&ph->pd, @@ -3662,16 +3667,20 @@ int kms_reenciper(const kms_handle_t handle, enum kms_reencipher_mode mode, printf("Completing re-enciphering of identity key.\n"); - rc = remove(ph->ekmf_config.identity_secure_key); + rc = plugin_remove_config_file(&ph->pd, + ph->ekmf_config.identity_secure_key); if (rc != 0) { - rc = -errno; _set_error(ph, "Failed to remove file '%s': %s", ph->ekmf_config.identity_secure_key, strerror(-rc)); goto out; } - rc = rename(reenc_file, ph->ekmf_config.identity_secure_key); + rc = renameat(ph->pd.config_path_fd, + plugin_basename(reenc_file), + ph->pd.config_path_fd, + plugin_basename( + ph->ekmf_config.identity_secure_key)); if (rc != 0) { rc = -errno; _set_error(ph, "Failed to rename file '%s' to '%s': %s", @@ -3851,7 +3860,7 @@ int kms_reenciper(const kms_handle_t handle, enum kms_reencipher_mode mode, out: if (rc != 0 && reenc_file != NULL) - remove(reenc_file); + plugin_remove_config_file(&ph->pd, reenc_file); if (reenc_file != NULL) free(reenc_file); diff --git a/zkey/kmip/zkey-kmip.c b/zkey/kmip/zkey-kmip.c index 0038fc984..91647282f 100644 --- a/zkey/kmip/zkey-kmip.c +++ b/zkey/kmip/zkey-kmip.c @@ -799,6 +799,12 @@ kms_handle_t kms_initialize(const char *config_path, bool verbose) ph->identity_secure_key = properties_get(ph->pd.properties, KMIP_CONFIG_IDENTITY_KEY); + if (ph->identity_secure_key != NULL && + !plugin_path_is_within_config(&ph->pd, ph->identity_secure_key)) { + rc = -EINVAL; + goto error; + } + ph->server = properties_get(ph->pd.properties, KMIP_CONFIG_SERVER); tmp = properties_get(ph->pd.properties, KMIP_CONFIG_PROFILE); @@ -1797,7 +1803,7 @@ static int _generate_identity_key(struct plugin_handle *ph, reenc_file = properties_get(ph->pd.properties, KMIP_CONFIG_IDENTITY_KEY_REENC); if (reenc_file != NULL) { - remove(reenc_file); + plugin_remove_config_file(&ph->pd, reenc_file); free(reenc_file); properties_remove(ph->pd.properties, KMIP_CONFIG_IDENTITY_KEY_REENC); @@ -1806,7 +1812,7 @@ static int _generate_identity_key(struct plugin_handle *ph, client_cert = properties_get(ph->pd.properties, KMIP_CONFIG_CLIENT_CERTIFICATE); if (client_cert != NULL) { - remove(client_cert); + plugin_remove_config_file(&ph->pd, client_cert); free(client_cert); properties_remove(ph->pd.properties, KMIP_CONFIG_CLIENT_CERTIFICATE); @@ -2994,7 +3000,7 @@ static int _configure_connection(struct plugin_handle *ph, if (rc != 0) goto out; } else { - remove(server_cert_file); + plugin_remove_config_file(&ph->pd, server_cert_file); } rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_SERVER_CERT, tls_trust_server_cert ? @@ -3013,7 +3019,7 @@ static int _configure_connection(struct plugin_handle *ph, if (rc != 0) goto out; } else { - remove(server_pubkey_file); + plugin_remove_config_file(&ph->pd, server_pubkey_file); } rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_SERVER_PUBKEY, @@ -3025,13 +3031,13 @@ static int _configure_connection(struct plugin_handle *ph, /* Remove any wrapping key properties from previous configuration */ file_name = properties_get(ph->pd.properties, KMIP_CONFIG_WRAPPING_KEY); if (file_name != NULL) { - remove(file_name); + plugin_remove_config_file(&ph->pd, file_name); free(file_name); } file_name = properties_get(ph->pd.properties, KMIP_CONFIG_WRAPPING_KEY_REENC); if (file_name != NULL) { - remove(file_name); + plugin_remove_config_file(&ph->pd, file_name); free(file_name); } rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_WRAPPING_KEY, @@ -3066,13 +3072,13 @@ static int _configure_connection(struct plugin_handle *ph, out: if (server_cert_temp != NULL) { - remove(server_cert_temp); + plugin_remove_config_file(&ph->pd, server_cert_temp); free(server_cert_temp); } if (server_cert_file != NULL) free(server_cert_file); if (server_pubkey_temp != NULL) { - remove(server_pubkey_temp); + plugin_remove_config_file(&ph->pd, server_pubkey_temp); free(server_pubkey_temp); } if (server_pubkey_file != NULL) @@ -3961,7 +3967,7 @@ static int _generate_wrapping_key(struct plugin_handle *ph, reenc_file = properties_get(ph->pd.properties, KMIP_CONFIG_WRAPPING_KEY_REENC); if (reenc_file != NULL) { - remove(reenc_file); + plugin_remove_config_file(&ph->pd, reenc_file); free(reenc_file); properties_remove(ph->pd.properties, KMIP_CONFIG_WRAPPING_KEY_REENC); @@ -3997,7 +4003,7 @@ static int _generate_wrapping_key(struct plugin_handle *ph, out: if (wrapping_key_file_tmp != NULL) { - remove(wrapping_key_file_tmp); + plugin_remove_config_file(&ph->pd, wrapping_key_file_tmp); free(wrapping_key_file_tmp); } if (wrapping_key_file != NULL) @@ -4360,15 +4366,15 @@ static int _complete_reencipher(struct plugin_handle *ph, if (key_file == NULL || reenc_file == NULL) goto out; - rc = remove(key_file); + rc = plugin_remove_config_file(&ph->pd, key_file); if (rc != 0) { - rc = -errno; _set_error(ph, "Failed to remove file '%s': %s", - key_file, strerror(-rc)); + key_file, strerror(-rc)); goto out; } - rc = rename(reenc_file, key_file); + rc = renameat(ph->pd.config_path_fd, plugin_basename(reenc_file), + ph->pd.config_path_fd, plugin_basename(key_file)); if (rc != 0) { rc = -errno; _set_error(ph, "Failed to rename file '%s' to '%s': %s", @@ -4604,9 +4610,18 @@ int kms_reenciper(const kms_handle_t handle, enum kms_reencipher_mode mode, KMIP_CONFIG_IDENTITY_KEY); if (ident_key_file == NULL) goto out; + if (!plugin_path_is_within_config(&ph->pd, ident_key_file)) { + rc = -EINVAL; + goto out; + } wrap_key_file = properties_get(ph->pd.properties, KMIP_CONFIG_WRAPPING_KEY); + if (wrap_key_file != NULL && + !plugin_path_is_within_config(&ph->pd, wrap_key_file)) { + rc = -EINVAL; + goto out; + } if (mode == KMS_REENC_MODE_STAGED) { util_asprintf(&ident_reenc_file, "%s/%s", ph->pd.config_path, @@ -4709,14 +4724,14 @@ int kms_reenciper(const kms_handle_t handle, enum kms_reencipher_mode mode, out: if (rc != 0 && ident_reenc_file != NULL) - remove(ident_reenc_file); + plugin_remove_config_file(&ph->pd, ident_reenc_file); if (ident_reenc_file != NULL) free(ident_reenc_file); if (ident_key_file != NULL) free(ident_key_file); if (rc != 0 && wrap_reenc_file != NULL) - remove(wrap_reenc_file); + plugin_remove_config_file(&ph->pd, wrap_reenc_file); if (wrap_reenc_file != NULL) free(wrap_reenc_file); if (wrap_key_file != NULL) diff --git a/zkey/plugin-utils.c b/zkey/plugin-utils.c index 2c9a7c4d0..9fc09581a 100644 --- a/zkey/plugin-utils.c +++ b/zkey/plugin-utils.c @@ -65,6 +65,7 @@ int plugin_init(struct plugin_data *pd, const char *plugin_name, util_assert(config_file != NULL, "Internal error: config_file is NULL"); memset(pd, 0, sizeof(struct plugin_data)); + pd->config_path_fd = -1; pd->plugin_name = util_strdup(plugin_name); pd->config_path = util_strdup(config_path); @@ -101,6 +102,15 @@ int plugin_init(struct plugin_data *pd, const char *plugin_name, S_IRGRP | S_IWGRP | S_IROTH); + pd->config_path_fd = open(config_path, + O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (pd->config_path_fd < 0) { + rc = -errno; + warnx("Failed to open directory '%s': %s", config_path, + strerror(-rc)); + goto error; + } + pd->properties = properties_new(); rc = plugin_load_config(pd); if (rc != 0 && rc != -EIO) { @@ -126,6 +136,10 @@ void plugin_term(struct plugin_data *pd) pr_verbose(pd, "Plugin terminated"); + if (pd->config_path_fd >= 0) { + close(pd->config_path_fd); + pd->config_path_fd = -1; + } if (pd->plugin_name != NULL) free((void *)pd->plugin_name); if (pd->config_path != NULL) @@ -233,7 +247,8 @@ int plugin_set_file_permission(struct plugin_data *pd, const char *filename) { int fd, rc = 0; - fd = open(filename, O_RDONLY | O_NOFOLLOW); + fd = openat(pd->config_path_fd, plugin_basename(filename), + O_RDONLY | O_NOFOLLOW); if (fd < 0) { rc = -errno; plugin_set_error(pd, "Failed to open '%s': %s", filename, @@ -327,6 +342,8 @@ int plugin_set_or_remove_property(struct plugin_data *pd, const char *name, * file (if existent), and then renaming the temporary file to the active file. * The active file permissions are also set to the permissions and the group of * configuration directory. + * All operations are descriptor-relative to config_path_fd so that no + * intermediate symlink can redirect them outside the config directory. * * @param pd the plugin data * @param temp_file the name of the temporary file @@ -337,20 +354,22 @@ int plugin_set_or_remove_property(struct plugin_data *pd, const char *name, int plugin_activate_temp_file(struct plugin_data *pd, const char *temp_file, const char *active_file) { + const char *active_base, *temp_base; int rc; - if (util_path_exists(active_file)) { - rc = remove(active_file); - if (rc != 0) { - rc = -errno; - plugin_set_error(pd, "remove failed on file '%s': %s", - active_file, strerror(-rc)); - return rc; - } + active_base = plugin_basename(active_file); + temp_base = plugin_basename(temp_file); + + if (unlinkat(pd->config_path_fd, active_base, 0) != 0 && + errno != ENOENT) { + rc = -errno; + plugin_set_error(pd, "remove failed on file '%s': %s", + active_file, strerror(-rc)); + return rc; } - rc = rename(temp_file, active_file); - if (rc != 0) { + if (renameat(pd->config_path_fd, temp_base, + pd->config_path_fd, active_base) != 0) { rc = -errno; plugin_set_error(pd, "rename failed on file '%s': %s", temp_file, strerror(-rc)); @@ -360,6 +379,73 @@ int plugin_activate_temp_file(struct plugin_data *pd, const char *temp_file, return plugin_set_file_permission(pd, active_file); } +/** + * Removes a file that resides directly inside the plugin config directory. + * Uses unlinkat(config_path_fd) so no intermediate symlink can redirect the + * operation outside the config directory. Only the basename of 'filename' + * is used; any directory prefix is ignored. + * + * @param pd the plugin data + * @param filename full path of the file to remove (only basename used) + * + * @returns 0 on success, or a negative errno value on failure + */ +int plugin_remove_config_file(struct plugin_data *pd, const char *filename) +{ + int rc = 0; + + if (filename == NULL) + return 0; + + if (unlinkat(pd->config_path_fd, plugin_basename(filename), 0) != 0) + rc = -errno; + + return rc; +} + +/** + * Checks whether 'path' resolves to a location strictly inside the plugin's + * config directory. Both the config directory and 'path' are canonicalized + * via canonicalize_file_name() so that symlinks and '..' components in + * 'path' cannot escape the config directory. + * + * @param pd the plugin data + * @param path the path to validate + * + * @returns true if path is inside the config directory, false otherwise. + * On failure (path cannot be resolved) false is returned and the + * plugin error message is set. + */ +bool plugin_path_is_within_config(struct plugin_data *pd, const char *path) +{ + char *real_config, *real_path; + size_t config_len; + bool ok = false; + + real_config = canonicalize_file_name(pd->config_path); + real_path = canonicalize_file_name(path); + + if (real_config == NULL || real_path == NULL) { + plugin_set_error(pd, "Failed to resolve path '%s': %s", + path, strerror(errno)); + goto out; + } + + config_len = strlen(real_config); + if (strncmp(real_path, real_config, config_len) == 0 && + real_path[config_len] == '/') + ok = true; + else + plugin_set_error(pd, + "'%s' is not inside the plugin config " + "directory '%s'", path, pd->config_path); + +out: + free(real_config); + free(real_path); + return ok; +} + /** * Check if the certificate is a self signed certificate, and if it is expired * or not yet valid. diff --git a/zkey/plugin-utils.h b/zkey/plugin-utils.h index 1454971e2..af501be39 100644 --- a/zkey/plugin-utils.h +++ b/zkey/plugin-utils.h @@ -15,9 +15,11 @@ #include <stdio.h> #include <errno.h> #include <err.h> +#include <fcntl.h> #include <stddef.h> #include <stdbool.h> #include <sys/stat.h> +#include <unistd.h> #include "kms-plugin.h" #include "cca.h" @@ -25,6 +27,7 @@ struct plugin_data { const char *plugin_name; const char *config_path; + int config_path_fd; mode_t config_path_mode; gid_t config_path_owner; const char *config_file; @@ -42,6 +45,14 @@ struct plugin_data { } \ } while (0) +static inline const char *plugin_basename(const char *path) +{ + const char *base; + + base = strrchr(path, '/'); + return base != NULL ? base + 1 : path; +} + int plugin_init(struct plugin_data *pd, const char *plugin_name, const char *config_path, const char *config_file, bool verbose); @@ -53,6 +64,8 @@ void plugin_set_error(struct plugin_data *pd, const char *fmt, ...); int plugin_load_config(struct plugin_data *pd); int plugin_save_config(struct plugin_data *pd); int plugin_set_file_permission(struct plugin_data *pd, const char *filename); +int plugin_remove_config_file(struct plugin_data *pd, const char *filename); +bool plugin_path_is_within_config(struct plugin_data *pd, const char *path); bool plugin_check_property(struct plugin_data *pd, const char *name); int plugin_set_or_remove_property(struct plugin_data *pd, const char *name, ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/s390-tools/vendor.tar.zst /work/SRC/openSUSE:Factory/.s390-tools.new.1258/vendor.tar.zst differ: char 7, line 1
