diff --git a/meson.build b/meson.build index 6770deed4ff..b624c6d8f02 100644 --- a/meson.build +++ b/meson.build @@ -3181,24 +3181,14 @@ if conf.get('ENABLE_HOSTNAMED') == 1 endif if conf.get('ENABLE_LOCALED') == 1 - if conf.get('HAVE_XKBCOMMON') == 1 - # logind will load libxkbcommon.so dynamically on its own, but we still - # need to specify where the headers are - deps = [libdl, - libxkbcommon.partial_dependency(compile_args: true), - userspace, - versiondep] - else - deps = [userspace, - versiondep] - endif - dbus_programs += executable( 'systemd-localed', systemd_localed_sources, include_directories : includes, link_with : [libshared], - dependencies : deps, + dependencies : libxkbcommon_deps + + [userspace, + versiondep], install_rpath : rootpkglibdir, install : true, install_dir : rootlibexecdir) diff --git a/src/locale/localed-util.c b/src/locale/localed-util.c index f9eb3e7e9f2..8ad5256ad9d 100644 --- a/src/locale/localed-util.c +++ b/src/locale/localed-util.c @@ -24,6 +24,7 @@ #include "string-util.h" #include "strv.h" #include "tmpfile-util.h" +#include "xkbcommon-util.h" static bool startswith_comma(const char *s, const char *prefix) { assert(s); @@ -154,6 +155,35 @@ int x11_context_copy(X11Context *dest, const X11Context *src) { return modified; } +int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error) { + int r; + + assert(xc); + + if (!x11_context_is_safe(xc)) { + if (error) + sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid X11 keyboard layout."); + return log_full_errno(log_level, SYNTHETIC_ERRNO(EINVAL), "Invalid X11 keyboard layout."); + } + + r = verify_xkb_rmlvo(xc->model, xc->layout, xc->variant, xc->options); + if (r == -EOPNOTSUPP) { + log_full_errno(MAX(log_level, LOG_NOTICE), r, + "Cannot verify if new keymap is correct, libxkbcommon.so unavailable."); + return 0; + } + if (r < 0) { + if (error) + sd_bus_error_set_errnof(error, r, "Specified keymap cannot be compiled, refusing as invalid."); + return log_full_errno(log_level, r, + "Cannot compile XKB keymap for x11 keyboard layout " + "(model='%s' / layout='%s' / variant='%s' / options='%s'): %m", + strempty(xc->model), strempty(xc->layout), strempty(xc->variant), strempty(xc->options)); + } + + return 0; +} + void vc_context_clear(VCContext *vc) { assert(vc); @@ -224,6 +254,46 @@ int vc_context_copy(VCContext *dest, const VCContext *src) { return modified; } +static int verify_keymap(const char *keymap, int log_level, sd_bus_error *error) { + int r; + + assert(keymap); + + r = keymap_exists(keymap); /* This also verifies that the keymap name is kosher. */ + if (r < 0) { + if (error) + sd_bus_error_set_errnof(error, r, "Failed to check keymap %s: %m", keymap); + return log_full_errno(log_level, r, "Failed to check keymap %s: %m", keymap); + } + if (r == 0) { + if (error) + sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Keymap %s is not installed.", keymap); + return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOENT), "Keymap %s is not installed.", keymap); + } + + return 0; +} + +int vc_context_verify_and_warn(const VCContext *vc, int log_level, sd_bus_error *error) { + int r; + + assert(vc); + + if (vc->keymap) { + r = verify_keymap(vc->keymap, log_level, error); + if (r < 0) + return r; + } + + if (vc->toggle) { + r = verify_keymap(vc->toggle, log_level, error); + if (r < 0) + return r; + } + + return 0; +} + void context_clear(Context *c) { assert(c); @@ -269,6 +339,7 @@ int locale_read_data(Context *c, sd_bus_message *m) { int vconsole_read_data(Context *c, sd_bus_message *m) { _cleanup_close_ int fd = -EBADF; struct stat st; + int r; assert(c); @@ -302,13 +373,24 @@ int vconsole_read_data(Context *c, sd_bus_message *m) { vc_context_clear(&c->vc); x11_context_clear(&c->x11_from_vc); - return parse_env_file_fd(fd, "/etc/vconsole.conf", - "KEYMAP", &c->vc.keymap, - "KEYMAP_TOGGLE", &c->vc.toggle, - "XKBLAYOUT", &c->x11_from_vc.layout, - "XKBMODEL", &c->x11_from_vc.model, - "XKBVARIANT", &c->x11_from_vc.variant, - "XKBOPTIONS", &c->x11_from_vc.options); + r = parse_env_file_fd( + fd, "/etc/vconsole.conf", + "KEYMAP", &c->vc.keymap, + "KEYMAP_TOGGLE", &c->vc.toggle, + "XKBLAYOUT", &c->x11_from_vc.layout, + "XKBMODEL", &c->x11_from_vc.model, + "XKBVARIANT", &c->x11_from_vc.variant, + "XKBOPTIONS", &c->x11_from_vc.options); + if (r < 0) + return r; + + if (vc_context_verify(&c->vc) < 0) + vc_context_clear(&c->vc); + + if (x11_context_verify(&c->x11_from_vc) < 0) + x11_context_clear(&c->x11_from_vc); + + return 0; } int x11_read_data(Context *c, sd_bus_message *m) { @@ -403,6 +485,9 @@ int x11_read_data(Context *c, sd_bus_message *m) { in_section = false; } + if (x11_context_verify(&c->x11_from_xorg) < 0) + x11_context_clear(&c->x11_from_xorg); + return 0; } @@ -589,6 +674,7 @@ int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) { for (unsigned n = 0;;) { _cleanup_strv_free_ char **a = NULL; + X11Context xc; r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); if (r < 0) @@ -601,13 +687,17 @@ int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) { if (!streq(vc->keymap, a[0])) continue; - return x11_context_copy(ret, - &(X11Context) { - .layout = empty_or_dash_to_null(a[1]), - .model = empty_or_dash_to_null(a[2]), - .variant = empty_or_dash_to_null(a[3]), - .options = empty_or_dash_to_null(a[4]), - }); + xc = (X11Context) { + .layout = empty_or_dash_to_null(a[1]), + .model = empty_or_dash_to_null(a[2]), + .variant = empty_or_dash_to_null(a[3]), + .options = empty_or_dash_to_null(a[4]), + }; + + if (x11_context_verify(&xc) < 0) + continue; + + return x11_context_copy(ret, &xc); } } @@ -755,6 +845,30 @@ int find_legacy_keymap(const X11Context *xc, char **ret) { return !!*ret; } +int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) { + _cleanup_free_ char *keymap = NULL; + int r; + + assert(xc); + assert(ret); + + if (isempty(xc->layout)) { + *ret = (VCContext) {}; + return 0; + } + + r = find_converted_keymap(xc, &keymap); + if (r == 0) + r = find_legacy_keymap(xc, &keymap); + if (r < 0) + return r; + + *ret = (VCContext) { + .keymap = TAKE_PTR(keymap), + }; + return 0; +} + int find_language_fallback(const char *lang, char **ret) { const char *map; _cleanup_fclose_ FILE *f = NULL; @@ -784,30 +898,6 @@ int find_language_fallback(const char *lang, char **ret) { } } -int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) { - _cleanup_free_ char *keymap = NULL; - int r; - - assert(xc); - assert(ret); - - if (isempty(xc->layout)) { - *ret = (VCContext) {}; - return 0; - } - - r = find_converted_keymap(xc, &keymap); - if (r == 0) - r = find_legacy_keymap(xc, &keymap); - if (r < 0) - return r; - - *ret = (VCContext) { - .keymap = TAKE_PTR(keymap), - }; - return 0; -} - bool locale_gen_check_available(void) { #if HAVE_LOCALEGEN if (access(LOCALEGEN_PATH, X_OK) < 0) { diff --git a/src/locale/localed-util.h b/src/locale/localed-util.h index 83d253c18d9..0c68f2942ee 100644 --- a/src/locale/localed-util.h +++ b/src/locale/localed-util.h @@ -43,6 +43,10 @@ void x11_context_empty_to_null(X11Context *xc); bool x11_context_is_safe(const X11Context *xc); bool x11_context_equal(const X11Context *a, const X11Context *b); int x11_context_copy(X11Context *dest, const X11Context *src); +int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error); +static inline int x11_context_verify(const X11Context *xc) { + return x11_context_verify_and_warn(xc, LOG_DEBUG, NULL); +} X11Context *context_get_x11_context(Context *c); @@ -52,6 +56,10 @@ bool vc_context_isempty(const VCContext *vc); void vc_context_empty_to_null(VCContext *vc); bool vc_context_equal(const VCContext *a, const VCContext *b); int vc_context_copy(VCContext *dest, const VCContext *src); +int vc_context_verify_and_warn(const VCContext *vc, int log_level, sd_bus_error *error); +static inline int vc_context_verify(const VCContext *vc) { + return vc_context_verify_and_warn(vc, LOG_DEBUG, NULL); +} int find_converted_keymap(const X11Context *xc, char **ret); int find_legacy_keymap(const X11Context *xc, char **ret); diff --git a/src/locale/localed.c b/src/locale/localed.c index ec5b78a1906..e08f8ac6efb 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -5,11 +5,6 @@ #include #include -#if HAVE_XKBCOMMON -#include -#include -#endif - #include "sd-bus.h" #include "alloc-util.h" @@ -19,7 +14,6 @@ #include "bus-message.h" #include "bus-polkit.h" #include "constants.h" -#include "dlfcn-util.h" #include "kbd-util.h" #include "localed-util.h" #include "macro.h" @@ -378,15 +372,9 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro vc_context_empty_to_null(&in); - FOREACH_STRING(name, in.keymap ?: in.toggle, in.keymap ? in.toggle : NULL) { - r = keymap_exists(name); /* This also verifies that the keymap name is kosher. */ - if (r < 0) { - log_error_errno(r, "Failed to check keymap %s: %m", name); - return sd_bus_error_set_errnof(error, r, "Failed to check keymap %s: %m", name); - } - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Keymap %s is not installed.", name); - } + r = vc_context_verify_and_warn(&in, LOG_ERR, error); + if (r < 0) + return r; r = vconsole_read_data(c, m); if (r < 0) { @@ -488,107 +476,6 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro return sd_bus_reply_method_return(m, NULL); } -#if HAVE_XKBCOMMON - -_printf_(3, 0) -static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) { - const char *fmt; - - fmt = strjoina("libxkbcommon: ", format); - DISABLE_WARNING_FORMAT_NONLITERAL; - log_internalv(LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__, fmt, args); - REENABLE_WARNING; -} - -#define LOAD_SYMBOL(symbol, dl, name) \ - ({ \ - (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \ - (symbol) ? 0 : -EOPNOTSUPP; \ - }) - -static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { - - /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge - * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function - * pointers to the shared library are below: */ - - struct xkb_context* (*symbol_xkb_context_new)(enum xkb_context_flags flags) = NULL; - void (*symbol_xkb_context_unref)(struct xkb_context *context) = NULL; - void (*symbol_xkb_context_set_log_fn)(struct xkb_context *context, void (*log_fn)(struct xkb_context *context, enum xkb_log_level level, const char *format, va_list args)) = NULL; - struct xkb_keymap* (*symbol_xkb_keymap_new_from_names)(struct xkb_context *context, const struct xkb_rule_names *names, enum xkb_keymap_compile_flags flags) = NULL; - void (*symbol_xkb_keymap_unref)(struct xkb_keymap *keymap) = NULL; - - const struct xkb_rule_names rmlvo = { - .model = model, - .layout = layout, - .variant = variant, - .options = options, - }; - struct xkb_context *ctx = NULL; - struct xkb_keymap *km = NULL; - _cleanup_(dlclosep) void *dl = NULL; - int r; - - /* Compile keymap from RMLVO information to check out its validity */ - - dl = dlopen("libxkbcommon.so.0", RTLD_LAZY); - if (!dl) - return -EOPNOTSUPP; - - r = LOAD_SYMBOL(symbol_xkb_context_new, dl, "xkb_context_new"); - if (r < 0) - goto finish; - - r = LOAD_SYMBOL(symbol_xkb_context_unref, dl, "xkb_context_unref"); - if (r < 0) - goto finish; - - r = LOAD_SYMBOL(symbol_xkb_context_set_log_fn, dl, "xkb_context_set_log_fn"); - if (r < 0) - goto finish; - - r = LOAD_SYMBOL(symbol_xkb_keymap_new_from_names, dl, "xkb_keymap_new_from_names"); - if (r < 0) - goto finish; - - r = LOAD_SYMBOL(symbol_xkb_keymap_unref, dl, "xkb_keymap_unref"); - if (r < 0) - goto finish; - - ctx = symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES); - if (!ctx) { - r = -ENOMEM; - goto finish; - } - - symbol_xkb_context_set_log_fn(ctx, log_xkb); - - km = symbol_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS); - if (!km) { - r = -EINVAL; - goto finish; - } - - r = 0; - -finish: - if (symbol_xkb_keymap_unref && km) - symbol_xkb_keymap_unref(km); - - if (symbol_xkb_context_unref && ctx) - symbol_xkb_context_unref(ctx); - - return r; -} - -#else - -static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { - return 0; -} - -#endif - static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { _cleanup_(vc_context_clear) VCContext converted = {}; Context *c = ASSERT_PTR(userdata); @@ -603,18 +490,9 @@ static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_err x11_context_empty_to_null(&in); - if (!x11_context_is_safe(&in)) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Received invalid keyboard data"); - - r = verify_xkb_rmlvo(in.model, in.layout, in.variant, in.options); - if (r == -EOPNOTSUPP) - log_notice_errno(r, "Cannot verify if new keymap is correct, libxkbcommon.so unavailable."); - else if (r < 0) { - log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m", - strempty(in.model), strempty(in.layout), strempty(in.variant), strempty(in.options)); - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, - "Specified keymap cannot be compiled, refusing as invalid."); - } + r = x11_context_verify_and_warn(&in, LOG_ERR, error); + if (r < 0) + return r; r = vconsole_read_data(c, m); if (r < 0) { diff --git a/src/locale/meson.build b/src/locale/meson.build index 60d2666ffd6..98a0b44d631 100644 --- a/src/locale/meson.build +++ b/src/locale/meson.build @@ -3,6 +3,7 @@ systemd_localed_sources = files( 'localed-util.c', 'localed.c', + 'xkbcommon-util.c', ) localectl_sources = files('localectl.c') @@ -28,11 +29,21 @@ if conf.get('ENABLE_LOCALED') == 1 install_dir : pkgdatadir) endif +# logind will load libxkbcommon.so dynamically on its own, but we still need to +# specify where the headers are. +if conf.get('HAVE_XKBCOMMON') == 1 + libxkbcommon_deps = [libdl, + libxkbcommon.partial_dependency(compile_args: true)] +else + libxkbcommon_deps = [] +endif + tests += [ { 'sources' : files( 'test-localed-util.c', 'localed-util.c', + 'xkbcommon-util.c', ), }, ] diff --git a/src/locale/xkbcommon-util.c b/src/locale/xkbcommon-util.c new file mode 100644 index 00000000000..295ac8af8db --- /dev/null +++ b/src/locale/xkbcommon-util.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dlfcn-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "xkbcommon-util.h" + +#if HAVE_XKBCOMMON +static void *xkbcommon_dl = NULL; + +struct xkb_context* (*sym_xkb_context_new)(enum xkb_context_flags flags); +void (*sym_xkb_context_unref)(struct xkb_context *context); +void (*sym_xkb_context_set_log_fn)( + struct xkb_context *context, + void (*log_fn)( + struct xkb_context *context, + enum xkb_log_level level, + const char *format, + va_list args)); +struct xkb_keymap* (*sym_xkb_keymap_new_from_names)( + struct xkb_context *context, + const struct xkb_rule_names *names, + enum xkb_keymap_compile_flags flags); +void (*sym_xkb_keymap_unref)(struct xkb_keymap *keymap); + +static int dlopen_xkbcommon(void) { + return dlopen_many_sym_or_warn( + &xkbcommon_dl, "libxkbcommon.so.0", LOG_DEBUG, + DLSYM_ARG(xkb_context_new), + DLSYM_ARG(xkb_context_unref), + DLSYM_ARG(xkb_context_set_log_fn), + DLSYM_ARG(xkb_keymap_new_from_names), + DLSYM_ARG(xkb_keymap_unref)); +} + +_printf_(3, 0) +static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) { + const char *fmt; + + fmt = strjoina("libxkbcommon: ", format); + DISABLE_WARNING_FORMAT_NONLITERAL; + log_internalv(LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__, fmt, args); + REENABLE_WARNING; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct xkb_context *, sym_xkb_context_unref, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct xkb_keymap *, sym_xkb_keymap_unref, NULL); + +int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { + _cleanup_(sym_xkb_context_unrefp) struct xkb_context *ctx = NULL; + _cleanup_(sym_xkb_keymap_unrefp) struct xkb_keymap *km = NULL; + const struct xkb_rule_names rmlvo = { + .model = model, + .layout = layout, + .variant = variant, + .options = options, + }; + int r; + + /* Compile keymap from RMLVO information to check out its validity */ + + r = dlopen_xkbcommon(); + if (r < 0) + return r; + + ctx = sym_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES); + if (!ctx) + return -ENOMEM; + + sym_xkb_context_set_log_fn(ctx, log_xkb); + + km = sym_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!km) + return -EINVAL; + + return 0; +} + +#endif diff --git a/src/locale/xkbcommon-util.h b/src/locale/xkbcommon-util.h new file mode 100644 index 00000000000..e99c2d7783d --- /dev/null +++ b/src/locale/xkbcommon-util.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#if HAVE_XKBCOMMON +#include + +extern struct xkb_context* (*sym_xkb_context_new)(enum xkb_context_flags flags); +extern void (*sym_xkb_context_unref)(struct xkb_context *context); +extern void (*sym_xkb_context_set_log_fn)( + struct xkb_context *context, + void (*log_fn)( + struct xkb_context *context, + enum xkb_log_level level, + const char *format, + va_list args)); +extern struct xkb_keymap* (*sym_xkb_keymap_new_from_names)( + struct xkb_context *context, + const struct xkb_rule_names *names, + enum xkb_keymap_compile_flags flags); +extern void (*sym_xkb_keymap_unref)(struct xkb_keymap *keymap); + +int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options); + +#else + +static inline int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { + return 0; +} + +#endif diff --git a/test/units/testsuite-73.sh b/test/units/testsuite-73.sh index 4f33cab8482..2aef7c779a4 100755 --- a/test/units/testsuite-73.sh +++ b/test/units/testsuite-73.sh @@ -552,6 +552,102 @@ test_convert() { assert_not_in "X11 Options:" "$output" } +test_validate() { + if [[ -z "$(localectl list-keymaps)" ]]; then + echo "No vconsole keymap installed, skipping test." + return + fi + + if [[ -z "$(localectl list-x11-keymap-layouts)" ]]; then + echo "No x11 keymap installed, skipping test." + return + fi + + backup_keymap + trap restore_keymap RETURN + + # clear previous settings + systemctl stop systemd-localed.service + wait_vconsole_setup + rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard + + # create invalid configs + cat >/etc/vconsole.conf </etc/vconsole.conf </etc/vconsole.conf </failed enable_debug @@ -559,6 +655,7 @@ test_locale test_vc_keymap test_x11_keymap test_convert +test_validate touch /testok rm /failed