From bba451c959e6ef62f8cb19e2b16da8e454d60d88 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Mon, 12 Oct 2026 11:26:28 +0000 Subject: [PATCH] Give the server its own session keyring on Linux The server otherwise keeps the session keyring of the login session that started it, which pam_keyinit(8) may revoke when that session ends, leaving the server and its panes unable to use the kernel keyring. Join a new session keyring with the user keyring linked in once the configuration file has been loaded, and revoke it when the server exits. This is controlled by a new session-keyring server option, on by default. --- Makefile.am | 5 +++++ cfg.c | 4 ++++ compat.h | 6 ++++++ compat/keyring-linux.c | 42 ++++++++++++++++++++++++++++++++++++++++++ configure.ac | 7 +++++++ options-table.c | 8 ++++++++ server.c | 3 +++ tmux.1 | 8 ++++++++ 8 files changed, 83 insertions(+) create mode 100644 compat/keyring-linux.c diff --git a/Makefile.am b/Makefile.am index 349d6e224..a45ecd515 100644 --- a/Makefile.am +++ b/Makefile.am @@ -239,6 +239,11 @@ if NEED_FORKPTY nodist_tmux_SOURCES += compat/forkpty-@PLATFORM@.c endif +# Add compat file for Linux session keyrings. +if HAVE_KEYCTL +nodist_tmux_SOURCES += compat/keyring-linux.c +endif + # Add compat file for systemd. if HAVE_SYSTEMD nodist_tmux_SOURCES += compat/systemd.c diff --git a/cfg.c b/cfg.c index 31294f92a..17c01ad64 100644 --- a/cfg.c +++ b/cfg.c @@ -62,6 +62,10 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data) } prompt_load_history(); +#ifdef HAVE_KEYCTL + if (options_get_number(global_options, "session-keyring")) + keyring_join(); +#endif return (CMD_RETURN_NORMAL); } diff --git a/compat.h b/compat.h index 66a22640b..0631b7252 100644 --- a/compat.h +++ b/compat.h @@ -441,6 +441,12 @@ void *reallocarray(void *, size_t, size_t); void *recallocarray(void *, size_t, size_t, size_t); #endif +#ifdef HAVE_KEYCTL +/* keyring-linux.c */ +void keyring_join(void); +void keyring_revoke(void); +#endif + #ifdef HAVE_SYSTEMD /* systemd.c */ int systemd_activated(void); diff --git a/compat/keyring-linux.c b/compat/keyring-linux.c new file mode 100644 index 000000000..e777bbbbb --- /dev/null +++ b/compat/keyring-linux.c @@ -0,0 +1,42 @@ +#include +#include + +#include +#include +#include +#include + +#include "tmux.h" + +/* Serial of the session keyring we joined, or 0 if none. */ +static long keyring_serial; + +/* + * Create a new session keyring since the current one belongs to the login + * session which we're meant to outlive (and pam_keyinit(8) is likely to revoke + * the login session's keyring when it ends). + */ +void +keyring_join(void) +{ + long serial; + + serial = syscall(SYS_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL); + if (serial == -1) { + log_debug("%s: join failed: %s", __func__, strerror(errno)); + return; + } + if (syscall(SYS_keyctl, KEYCTL_LINK, KEY_SPEC_USER_KEYRING, + KEY_SPEC_SESSION_KEYRING) == -1) + log_debug("%s: link failed: %s", __func__, strerror(errno)); + keyring_serial = serial; +} + +void +keyring_revoke(void) +{ + if (keyring_serial == 0) + return; + if (syscall(SYS_keyctl, KEYCTL_REVOKE, keyring_serial) == -1) + log_debug("%s: revoke failed: %s", __func__, strerror(errno)); +} diff --git a/configure.ac b/configure.ac index 5da5f30af..139d93b88 100644 --- a/configure.ac +++ b/configure.ac @@ -795,6 +795,13 @@ else AC_LIBOBJ(daemon) fi +# Look for linux/keyctl.h, compat/keyring-linux.c used if found. +AC_CHECK_HEADER(linux/keyctl.h, found_keyctl=yes, found_keyctl=no) +if test "x$found_keyctl" = xyes; then + AC_DEFINE(HAVE_KEYCTL) +fi +AM_CONDITIONAL(HAVE_KEYCTL, [test "x$found_keyctl" = xyes]) + # Look for stravis, compat/{vis,unvis}.c used if missing. AC_CHECK_FUNC(stravis, found_stravis=yes, found_stravis=no) if test "x$found_stravis" = xyes; then diff --git a/options-table.c b/options-table.c index a65694973..813fc7b26 100644 --- a/options-table.c +++ b/options-table.c @@ -532,6 +532,14 @@ const struct options_table_entry options_table[] = { .text = "Maximum number of commands to keep in history." }, + { .name = "session-keyring", + .type = OPTIONS_TABLE_FLAG, + .scope = OPTIONS_TABLE_SERVER, + .default_num = 1, + .text = "Whether the server should join a new session keyring " + "(Linux only)." + }, + { .name = "set-clipboard", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SERVER, diff --git a/server.c b/server.c index be4427954..bad77f5b8 100644 --- a/server.c +++ b/server.c @@ -259,6 +259,9 @@ server_start(struct tmuxproc *client, uint64_t flags, struct event_base *base, job_kill_all(); prompt_save_history(); +#ifdef HAVE_KEYCTL + keyring_revoke(); +#endif exit(0); } diff --git a/tmux.1 b/tmux.1 index cf82a209f..b843d7e65 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5096,6 +5096,14 @@ each client. .It Ic prompt\-history\-limit Ar number Set the number of history items to save in the history file for each type of command prompt. +.It Xo Ic session\-keyring +.Op Cm on | off +.Xc +On Linux, if enabled (the default), the server joins a new session keyring with +the user keyring linked into it, rather than keeping the session keyring of the +login session it was started from, which may be revoked when that session ends. +The new keyring is revoked when the server exits. +This option is checked once the configuration file has been loaded. .It Xo Ic set\-clipboard .Op Cm on | external | off .Xc