Issue OSC ANSI sequence whenever we change "context" of a TTY, i.e. acquire privs, enter container or VM or similar (#35224)

This is mostly a strawman to get a discussion going regarding how to
communicate to terminal emulators such as ptyxis about run0 (and nspawn,
and vmspawn, and moe) and what it does.

It's hierarchical and I think still relatively simple.

/cc @chergert
This commit is contained in:
Lennart Poettering
2025-02-27 22:12:54 +01:00
committed by GitHub
30 changed files with 1208 additions and 49 deletions
+339
View File
@@ -0,0 +1,339 @@
---
title: OSC 3008: Hierarchical Context Signalling
category: Interfaces
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# OSC 3008: Hierarchical Context Signalling
A terminal connects a user with programs. Control of the program side of
terminals is typically passed around to various different components while the
user is active: a shell might pass control to a process it invokes. If that
process is `run0` then primary control is passed to the privileged session of
the target user. If `systemd-nspawn` is invoked to start a container, primary
control is passed to that container, and so on.
A terminal emulator might be interested to know which component is currently in
primary control of the program side of a terminal. OSC 3008 is a mechanism to
inform it about such contexts. Each component taking over control can inform
the terminal emulators that a new context begins now, and then use the terminal
or pass control down to further apps, which can introduce contexts. Each
context may carry various descriptive metadata fields.
## Status
This OSC sequence has been invented by the systemd project and is generated by
systemd. Currently, no terminal application is known that consumes these
sequences.
## Use Cases
Terminal emulators can use hierarchical context information:
1. To introduce markers/bookmarks in the output that the user can jump between.
2. To visually identify output from different contexts. For example the
background of the associated output can be tinted in a reddish tone when
privileges are acquired, and similar.
3. Meta information on specific output can be shown in a tooltip or similar
4. Programs (and all subcontexts) can be killed via a right-click menu on the
output they generate.
5. Similar, a right-click menu might offer an item to offer opening a new
interactive shell in the same working directory that was current on the
selected context.
6. Failed commands or aborted sessions can be marked requesting user attention.
## Context Types
There are various types of contexts defined by this specification:
1. `boot` → a booted system initiates this context early at boot. (systemd's
PID 1 generates this on `/dev/console`.)
2. `container` → a container manager initialized an interactive connection to a
container. (`systemd-nspawn` generates this when interactively invoking a
container. `machinectl login`, `machinectl shell` do this too.)
3. `vm` → a VM manager initialized a terminal connection to a
VM. (`systemd-vmspawn` generates this when interactively invoking a VM, as
one example.)
4. `elevate` → when the user interactively acquired higher privileges. (`run0`
initiates a context of this type whenever the user invokes it to acquire
root privileges.)
5. `chpriv` → similar, but when the user acquired *different* privileges, not
necessarily higher ones. (`run0` initiates a context of this type whenever
the user invokes it to acquire non-root privileges of another user.)
5. `subcontext` → similar, but the source and target privileges where
identical. (`run0` initiates a context of this type whenever the user
invokes it to acquire privileges of the user itself.)
6. `remote` → a user invoked a tool such as `ssh` to connect to a remote
system.
7. `shell` → an interactive terminal shell initiates this context
8. `command` → a shell interactively invokes a new program.
9. `app` → an interactive program may initiate this context.
10. `service` → the service manager invokes an interactive service on the terminal
11. `session` → a login session of the user is initialized.
## Semantics
Contexts in the sense of OSC 3008 are hierarchical, and describe a tree
structure: whenever a new context is opened it becomes the new active context,
and the previously active context becomes its parent (if there is one). Only
one context is currently active, but previously opened contexts remain valid in
the background. Any other data written or read should be considered associated
with the currently active context.
Each context carries an identifier, chosen by the component opening the
context. The identifier can chosen freely, but must not be longer than 64
characters. The characters may be in the 32…126 byte range. Identifiers should
be universally unique, for example randomly generated. A freshly generated UUID
would work well for this, but this could also be something like the Linux boot
ID combined with the 64bit inode number of Linux pidfds, or something hashed
from it.
Fundamentally, there are two OSC 3008 commands defined:
1. OSC "`3008;start=`" … (the *start sequence*) → this initiates, updates or
indicates a return to a context. It carries a context identifier, and
typically some metadata. This may be sent to first initiate a context. If
sent again for the same context ID that was initiated already this indicates
an update of the existing context. In this case, *any* previously set
metadata fields for the context are flushed out, reset to their defaults,
and then reinitialized from the newly supplied data. Also, in this case any
subcontexts of the contexts are implicitly terminated.
2. OSC "`3008;end=`" … (the *end sequence*) → this terminates a context. It
carries a context identifier to close, initiated before with OSC
"`3008;start=`". It may also carry additional metadata.
## General Syntax
This builds on ECMA-48, and reuses the OSC and ST concepts introduced there.
For sequences following this specification it is recommended to encode OSC as
0x1B 0x5D, and ST as 0x1B 0x5C.
ECMA-48 only allows characters from the range 0x20…0x7e (i.e. 32…126) inside
OSC sequences. However, most terminal emulators nowadays allow the ASCII byte
range > 0x7f in the OSC sequences they process, and so does this
specification. Control characters (< 0x20 and 0x7f) are not allowed. The
semicolon character ("`;`") – which is used as field separator by this
specification – shall be replaced by "`\x3b`" and the backslash character
("`\`") shall be replaced by "`\x5c`". All textual fields must be encoded in
UTF-8, and then escaped with these two replacements.
The start sequence begins with OSC, followed by the string `3008;start=`,
followed by the context ID. This is then followed by any number of metadata
fields, including none. Metadata fields begin with a semicolon (`;`) followed
by in a string identifying the type of field, followed by an equal sign (`=`),
and the field value. The sequence ends in ST.
The end sequence begins with OSC, followed by the string `3008;end=`, followed
by the context ID, and a series of metadata fields in the same syntax as for
the start sequence. The sequence ends in ST.
## Metadata Fields
The following fields are currently defined for the start sequence:
| Field | Context Types | Description |
|---------------|---------------|-------------------------------------------------------------------------------------------------------------|
| `type=` | *all* | Declares the context type, one of the types described above |
| `user=` | *all* | UNIX user name the process issuing the sequence runs as |
| `hostname=` | *all* | UNIX host name of the system the process issuing the sequence runs on |
| `machineid=` | *all* | The machine ID (i.e. `/etc/machine-id`) of the system the process issuing the sequence runs on |
| `bootid=` | *all* | The boot ID (i.e. `/proc/sys/kernel/random/boot_id`) of the system the process issuing the sequence runs on |
| `pid=` | *all* | The numeric PID of the process issuing the sequence, in decimal notation |
| `pidfdid=` | *all* | The 64bit inode number of the pidfd of the process issuing the sequence, in decimal notation |
| `comm=` | *all* | The process name (i.e. `/proc/$PID/comm`, `PR_GET_NAME`) of the process issuing the sequence |
| `cwd=` | `shell`, `command` | The current working directory |
| `cmdline=` | `command` | The full command line of the invoked command |
| `vm=` | `vm` | The name of the VM being invoked |
| `container=` | `container` | The name of the container being invoked |
| `targetuser=` | `elevate`, `chpriv`, `vm`, `container`, `remote`, `session` | Target UNIX user name |
| `targethost=` | `remote` | Target UNIX, DNS host name, or IP address |
| `sessionid=` | `session` | New allocated session ID |
The following fields are currently defined for the end sequence:
| Field | Context Types | Description |
|---------------|---------------|-------------------------------------------------------------------------------------------------------------|
| `exit=` | `command` | One of `success`, `failure`, `crash`, `interrupt`, indicating how the program terminated |
| `status=` | `command` | The command's numeric exit status, i.e. the 0…255 value a program returns |
| `signal=` | `command` | The termination signal of the command, if it died abnormally. A symbolic signal name. (`SIGKILL`, …) |
All fields are optional, including the context type. However, it is generally
recommended to always include the first 7 fields listed above, to make it easy
to pinpoint the origin of a context in a race-free fashion, without any
ambiguities.
The order of the metadata fields is undefined, they may appear in any order
(including that `type=` is specified at the very end or in the middle!). Note
that `start=` and `end=` are not considered metadata fields but part of the
start sequence, and hence must always appear right after OSC.
## Processing, Limits, Security
All context information provided like this should be considered auxiliary and –
to some degree – redundant information. Hence, it would be wise for a terminal
to enforce limits on various resources, dropping additional data once these
limits are hit. Most importantly, a maximum stacking depth should probably
enforced: any attempts to initiate further contexts should be ignored once the
stack limit is hit (i.e. the earlier contexts should be kept, the later
contexts be discarded, not the opposite). Overly long fields should be
discarded (or potentially truncated, depending on the field type). This
specification does not recommend any specific stack or string limits for now.
The usual terminal reset sequences should *not* effect the stack of contexts
(this is a safety feature: a program down the stack should not be able to
affect the stack further up, possibly hiding relevant information). A temporary
TTY hangup (`vhangup()`) should result in a full reset of the stack.
All provided data should be processed in a lenient, graceful fashion: if a
sequence contains invalid fields, those fields should be ignored, but the rest
of the fields should still be used. In particular, unknown fields should be
ignored.
The fields provided in these sequences should not contain sensitive
information. Context IDs should not be considered confidential, but it is
strongly recommended to generate them in a fashion that guarantees their
sufficient uniqueness and avoids accidental or intended clashes with other
contents.
## Examples
1. A new container `foobar` has been invoked by user `lennart` on host `zeta`:
`OSC "3008;start=bed86fab93af4328bbed0a1224af6d40;type=container;user=lennart;hostname=zeta;machineid=3deb5353d3ba43d08201c136a47ead7b;bootid=d4a3d0fdf2e24fdea6d971ce73f4fbf2;pid=1062862;pidfdid=1063162;comm=systemd-nspawn;container=foobar" ST`
2. This context ends: `OSC "3008;end=bed86fab93af4328bbed0a1224af6d40" ST`
## Syntax in ABNF
```abnf
OSC = %x1B %x5D
ST = %x1B %x5C
DECIMAL = "0"-"9"
HEX = "0"-"9" / "A"-"F" / "a-f"
ID128 = 32*36(HEX / "-")
UINT64 = 1*20DECIMAL
ESCSEMICOLON = "\x3b"
ESCBACKSLASH = "\x5c"
SAFE = %x20-3a / %x3c-5b / %x5d-7e / ESCSEMICOLON / ESCBACKSLASH
CTXID = 1*64SAFE
TYPEENUM = "service" / "session" / "shell" / "command" / "vm" / "container" / "elevate" / "chpriv" / "subcontext" / "remote" / "boot" / "app"
TYPE = "type=" TYPEENUM
USER = "user=" 1*255SAFE
HOSTNAME = "hostname=" 1*255SAFE
MACHINEID = "machineid=" 1D128
BOOTID = "bootid=" ID128
PID = "pid=" UINT64
PIDFDID = "pidfdid=" UINT64
COMM = "comm=" 1*255SAFE
CWD = "cwd=" 1*255SAFE
CMDLINE = "cmdline=" *255SAFE
VM = "vm=" 1*255SAFE
CONTAINER = "container=" 1*255SAFE
TARGETUSER = "targetuser=" 1*255SAFE
TARGETHOST = "targethost=" 1*255SAFE
SESSIONID = "sessionid=" 1*255SAFE
STARTFIELD = TYPE / USER / HOSTNAME / MACHINEID / BOOTID / PID / PIDFDID / COMM / CWD / CMDLINE / VM / CONTAINER / TARGETUSER / TARGETHOST / SESSIONID
STARTSEQ = OSC "3008;start=" CTXID *(";" STARTFIELD) ST
EXITENUM = "success" / "failure" / "crash" / "interrupt"
SIGNALENUM = "SIGBUS" / "SIGTRAP" / "SIGABRT" / "SIGSEGV" / …
EXIT = "exit=" EXITENUM
STATUS = "status=" UINT64
SIGNAL = "signal=" SIGNALENUM
ENDFIELD = EXIT / STATUS / SIGNAL
ENDSEQ = OSC "3008;end=" CTXID *(";" ENDFIELD) ST
```
## Known OSC Prefixes
Here's a list of OSC prefixes used by the various sequences currently in public
use in various terminal emulators. It's not going to be complete, but I tried
to do some reasonably thorough research to avoid conflicts with the new OSC
sequence defined above.
| OSC Prefix | Purpose |
|----------------:|------------------------------------------------------------|
| `OSC "0;…"` | Icon name + window title |
| `OSC "1;…"` | Icon name |
| `OSC "2;…"` | Window title |
| `OSC "3;…"` | X11 property |
| `OSC "4;…"` | Palette |
| `OSC "5;…"` | Special palette |
| `OSC "6;…"` | Disable special color |
| `OSC "7;…"` | Report cwd |
| `OSC "8;…"` | Hyperlink |
| `OSC "9;…"` | Progress bar (conemu) [conflict: also growl notifications] |
| `OSC "10;…"` | Change colors |
| `OSC "11;…"` | " |
| `OSC "12;…"` | " |
| `OSC "13;…"` | " |
| `OSC "14;…"` | " |
| `OSC "15;…"` | " |
| `OSC "16;…"` | " |
| `OSC "17;…"` | " |
| `OSC "18;…"` | " |
| `OSC "19;…"` | " |
| `OSC "21;…"` | Query colors (kitty) |
| `OSC "22;…"` | Cursor shape |
| `OSC "46;…"` | Log file |
| `OSC "50;…"` | Set font |
| `OSC "51;…"` | Emacs shell |
| `OSC "52;…"` | Manipulate selection data (aka clipboard) |
| `OSC "60;…"` | Query allowed |
| `OSC "61;…"` | Query disallowed |
| `OSC "99;…"` | Notifications (kitty) |
| `OSC "104;…"` | Reset color |
| `OSC "105;…"` | Enable/disable special color |
| `OSC "110;…"` | Reset colors |
| `OSC "111;…"` | " |
| `OSC "112;…"` | " |
| `OSC "113;…"` | " |
| `OSC "114;…"` | " |
| `OSC "115;…"` | " |
| `OSC "116;…"` | " |
| `OSC "117;…"` | " |
| `OSC "118;…"` | " |
| `OSC "119;…"` | " |
| `OSC "133;…"` | Prompt/command begin/command end (finalterm/iterm2) |
| `OSC "440;…"` | Audio (mintty) |
| `OSC "633;…"` | vscode action (Windows Terminal) |
| `OSC "666;…"` | "termprop" (vte) |
| `OSC "701;…"` | Locale (mintty) |
| `OSC "777;…"` | Notification (rxvt) |
| `OSC "3008;…"` | This specification |
| `OSC "7704;…"` | ANSI colors (mintty) |
| `OSC "7750;…"` | Emoji style (mintty) |
| `OSC "7770;…"` | Font size (mintty) |
| `OSC "7771;…"` | Glyph coverage (mintty) |
| `OSC "7721:…"` | Copy window title (mintty) |
| `OSC "7777;…"` | Window size (mintty) |
| `OSC "9001;…"` | Action (Windows Terminal) |
| `OSC "1337;…"` | iterm2 multiplex seeuqnece |
| `OSC "5522;…"` | Clipboard (kitty) |
| `OSC "30001;…"` | Push color onto stack (kitty) |
| `OSC "30101;…"` | Pop color from stack (kitty) |
| `OSC "77119;…"` | Wide chars (mintty) |
+1
View File
@@ -233,6 +233,7 @@ if shellprofiledir == ''
shellprofiledir = sysconfdir / 'profile.d'
endif
conf.set10('LINK_SHELL_EXTRA_DROPIN', shellprofiledir != 'no' and not shellprofiledir.startswith('/usr/'))
conf.set10('LINK_OSC_CONTEXT_DROPIN', shellprofiledir != 'no' and not shellprofiledir.startswith('/usr/'))
conf.set('SHELLPROFILEDIR', shellprofiledir, description : 'shell profile directory')
memory_accounting_default = get_option('memory-accounting-default')
+59
View File
@@ -0,0 +1,59 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck shell=bash
# shellcheck disable=SC2016
# shellcheck disable=SC1003
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
__systemd_osc_context_escape() {
# Escape according to the OSC 8003 spec. Since this requires shelling out
# to 'sed' we'll only do it where it's strictly necessary, and skip it when
# processing strings we are pretty sure we won't need it for, such as
# uuids, id128, hostnames, usernames, since they all come with syntax
# requirements that exclude \ and ; anyway. This hence primarily is about
# escaping the current working directory.
echo "$1" | sed -e 's/\\/\\x5x/g' -e 's/;/\\x3b/g'
}
__systemd_osc_context_common() {
printf ";user=%s;hostname=%s;machineid=%s;bootid=%s;pid=%s" "$USER" "$HOSTNAME" "$(</etc/machine-id)" "$(</proc/sys/kernel/random/boot_id)" "$$"
}
__systemd_osc_context_precmdline() {
local systemd_exitstatus="$?"
# Close previous command
if [ -n "${systemd_osc_context_cmd_id:-}" ]; then
if [ "$systemd_exitstatus" -ge 127 ]; then
printf "\033]8003;end=%s;exit=interrupt;signal=%s\033\\" "$systemd_osc_context_cmd_id" $((systemd_exitstatus-127))
elif [ "$systemd_exitstatus" -ne 0 ]; then
printf "\033]8003;end=%s;exit=failure;status=%s\033\\" "$systemd_osc_context_cmd_id" $((systemd_exitstatus))
else
printf "\033]8003;end=%s;exit=success\033\\" "$systemd_osc_context_cmd_id"
fi
fi
# Prepare a context ID for this shell if we have none
if [ -z "${systemd_osc_context_shell_id:-}" ]; then
read -r systemd_osc_context_shell_id </proc/sys/kernel/random/uuid
fi
# Create or update the shell session
printf "\033]8003;start=%s%s;type=shell;cwd=%s\033\\" "$systemd_osc_context_shell_id" "$(__systemd_osc_context_common)" "$(__systemd_osc_context_escape "$PWD")"
# Prepare cmd id for next command
read -r systemd_osc_context_cmd_id </proc/sys/kernel/random/uuid
}
if [[ -n "${BASH_VERSION:-}" ]]; then
# Whenever a new prompt is shown close the previous command, and prepare new command
PROMPT_COMMAND+=(__systemd_osc_context_precmdline)
# PS0 is shown right after a prompt completed, but before the command is executed
PS0='\033]8003;start=$systemd_osc_context_cmd_id$(__systemd_osc_context_common);type=command;cwd=$(__systemd_osc_context_escape "$PWD")\033\\'"${PS0:-}"
fi
+8
View File
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
install_data('70-systemd-shell-extra.sh', install_dir : shellprofiledir.startswith('/usr/') ? shellprofiledir : libexecdir / 'profile.d')
install_data('80-systemd-osc-context.sh', install_dir : shellprofiledir.startswith('/usr/') ? shellprofiledir : libexecdir / 'profile.d')
if conf.get('LINK_SHELL_EXTRA_DROPIN') == 1
install_emptydir(shellprofiledir)
@@ -8,3 +9,10 @@ if conf.get('LINK_SHELL_EXTRA_DROPIN') == 1
meson.add_install_script(sh, '-c',
ln_s.format(libexecdir / 'profile.d' / '70-systemd-shell-extra.sh', shellprofiledir / '70-systemd-shell-extra.sh'))
endif
if conf.get('LINK_OSC_CONTEXT_DROPIN') == 1
install_emptydir(shellprofiledir)
meson.add_install_script(sh, '-c',
ln_s.format(libexecdir / 'profile.d' / '80-systemd-osc-context.sh', shellprofiledir / '80-systemd-osc-context.sh'))
endif
+9 -12
View File
@@ -1930,38 +1930,35 @@ int terminal_set_cursor_position(int fd, unsigned row, unsigned column) {
return loop_write(fd, cursor_position, SIZE_MAX);
}
int terminal_reset_defensive(int fd, bool switch_to_text) {
int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
int r = 0;
assert(fd >= 0);
assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
/* Resets the terminal comprehensively, but defensively. i.e. both resets the tty via ioctl()s and
* via ANSI sequences, but avoids the latter in case we are talking to a pty. That's a safety measure
* because ptys might be connected to shell pipelines where we cannot expect such ansi sequences to
* work. Given that ptys are generally short-lived (and not recycled) this restriction shouldn't hurt
* much.
*
* The specified fd should be open for *writing*! */
/* Resets the terminal comprehensively, i.e. via both ioctl()s and via ANSI sequences, but do so only
* if $TERM is unset or set to "dumb" */
if (!isatty_safe(fd))
return -ENOTTY;
RET_GATHER(r, terminal_reset_ioctl(fd, switch_to_text));
RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
if (terminal_is_pty_fd(fd) == 0)
if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
(FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
RET_GATHER(r, terminal_reset_ansi_seq(fd));
return r;
}
int terminal_reset_defensive_locked(int fd, bool switch_to_text) {
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
assert(fd >= 0);
_cleanup_close_ int lock_fd = lock_dev_console();
if (lock_fd < 0)
log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m");
return terminal_reset_defensive(fd, switch_to_text);
return terminal_reset_defensive(fd, flags);
}
void termios_disable_echo(struct termios *termios) {
+8 -2
View File
@@ -41,8 +41,14 @@
bool isatty_safe(int fd);
int terminal_reset_defensive(int fd, bool switch_to_text);
int terminal_reset_defensive_locked(int fd, bool switch_to_text);
typedef enum TerminalResetFlags {
TERMINAL_RESET_SWITCH_TO_TEXT = 1 << 0,
TERMINAL_RESET_AVOID_ANSI_SEQ = 1 << 1,
TERMINAL_RESET_FORCE_ANSI_SEQ = 1 << 2,
} TerminalResetFlags;
int terminal_reset_defensive(int fd, TerminalResetFlags flags);
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags);
int terminal_set_cursor_position(int fd, unsigned row, unsigned column);
+17 -4
View File
@@ -46,6 +46,7 @@
#include "missing_securebits.h"
#include "missing_syscall.h"
#include "mkdir-label.h"
#include "osc-context.h"
#include "proc-cmdline.h"
#include "process-util.h"
#include "psi-util.h"
@@ -668,7 +669,7 @@ static int setup_confirm_stdio(
if (r < 0)
return r;
r = terminal_reset_defensive(fd, /* switch_to_text= */ true);
r = terminal_reset_defensive(fd, TERMINAL_RESET_SWITCH_TO_TEXT);
if (r < 0)
return r;
@@ -4367,6 +4368,10 @@ static void prepare_terminal(
p->stdout_fd >= 0))
return;
/* Let's explicitly determine whether to reset via ANSI sequences or not, taking our ExecContext
* information into account */
bool use_ansi = exec_context_shall_ansi_seq_reset(context);
if (context->tty_reset) {
/* When we are resetting the TTY, then let's create a lock first, to synchronize access. This
* in particular matters as concurrent resets and the TTY size ANSI DSR logic done by the
@@ -4375,10 +4380,16 @@ static void prepare_terminal(
if (lock_fd < 0)
log_exec_debug_errno(context, p, lock_fd, "Failed to lock /dev/console, ignoring: %m");
(void) terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false);
/* We explicitly control whether to send ansi sequences or not here, since we want to consult
* the env vars explicitly configured in the ExecContext, rather than our own environment
* block. */
(void) terminal_reset_defensive(STDOUT_FILENO, use_ansi ? TERMINAL_RESET_FORCE_ANSI_SEQ : TERMINAL_RESET_AVOID_ANSI_SEQ);
}
(void) exec_context_apply_tty_size(context, STDIN_FILENO, STDOUT_FILENO, /* tty_path= */ NULL);
if (use_ansi)
(void) osc_context_open_service(p->unit_id, p->invocation_id, /* ret_seq= */ NULL);
}
int exec_invoke(
@@ -4555,8 +4566,10 @@ int exec_invoke(
* disallocate the VT), to get rid of any prior uses of the device. Note that we do not keep any fd
* open here, hence some of the settings made here might vanish again, depending on the TTY driver
* used. A 2nd ("constructive") initialization after we opened the input/output fds we actually want
* will fix this. */
exec_context_tty_reset(context, params);
* will fix this. Note that we pass a NULL invocation ID here – as exec_context_tty_reset() expects
* the invocation ID associated with the OSC 3008 context ID to close. But we don't want to close any
* OSC 3008 context here, and opening a fresh OSC 3008 context happens a bit further down. */
exec_context_tty_reset(context, params, /* invocation_id= */ SD_ID128_NULL);
if (params->shall_confirm_spawn && exec_context_shall_confirm_spawn(context)) {
_cleanup_free_ char *cmdline = NULL;
+47 -8
View File
@@ -39,6 +39,7 @@
#include "format-util.h"
#include "glob-util.h"
#include "hexdecoct.h"
#include "io-util.h"
#include "ioprio-util.h"
#include "lock-util.h"
#include "log.h"
@@ -50,6 +51,7 @@
#include "missing_prctl.h"
#include "mkdir-label.h"
#include "namespace.h"
#include "osc-context.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
@@ -129,11 +131,12 @@ int exec_context_apply_tty_size(
rows == UINT_MAX ? &rows : NULL,
cols == UINT_MAX ? &cols : NULL);
/* If we got nothing so far and we are talking to a physical device, and the TTY reset logic is on,
* then let's query dimensions from the ANSI driver. */
/* If we got nothing so far and we are talking to a physical device, then let's query dimensions from
* the ANSI terminal driver. Note that we will not bother with this in case terminal reset via ansi
* sequences is not enabled, as the DSR logic relies on ANSI sequences after all, and if we shall not
* use those during initialization we need to skip it. */
if (rows == UINT_MAX && cols == UINT_MAX &&
context->tty_reset &&
terminal_is_pty_fd(output_fd) == 0 &&
exec_context_shall_ansi_seq_reset(context) &&
isatty_safe(input_fd)) {
r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &cols);
if (r < 0)
@@ -143,7 +146,7 @@ int exec_context_apply_tty_size(
return terminal_set_size_fd(output_fd, tty_path, rows, cols);
}
void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) {
void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p, sd_id128_t invocation_id) {
_cleanup_close_ int _fd = -EBADF, lock_fd = -EBADF;
int fd, r;
@@ -178,12 +181,32 @@ void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p)
log_warning_errno(lock_fd, "Failed to lock /dev/console, proceeding without lock: %m");
if (context->tty_reset)
(void) terminal_reset_defensive(fd, /* switch_to_text= */ true);
(void) terminal_reset_defensive(
fd,
TERMINAL_RESET_SWITCH_TO_TEXT |
(exec_context_shall_ansi_seq_reset(context) ? TERMINAL_RESET_FORCE_ANSI_SEQ : TERMINAL_RESET_AVOID_ANSI_SEQ));
r = exec_context_apply_tty_size(context, fd, fd, path);
if (r < 0)
log_debug_errno(r, "Failed to configure TTY dimensions, ignoring: %m");
if (!sd_id128_is_null(invocation_id)) {
sd_id128_t context_id;
r = osc_context_id_from_invocation_id(invocation_id, &context_id);
if (r < 0)
log_debug_errno(r, "Failed to derive context ID from invocation ID, ignoring: %m");
else {
_cleanup_free_ char *seq = NULL;
r = osc_context_close(context_id, &seq);
if (r < 0)
log_debug_errno(r, "Failed to acquire OSC close sequence, ignoring: %m");
else
(void) loop_write(fd, seq, SIZE_MAX);
}
}
if (context->tty_vhangup)
(void) terminal_vhangup_fd(fd);
@@ -974,6 +997,22 @@ bool exec_context_may_touch_console(const ExecContext *ec) {
tty_may_match_dev_console(exec_context_tty_path(ec));
}
bool exec_context_shall_ansi_seq_reset(const ExecContext *c) {
assert(c);
/* Determines whether ANSI sequences shall be used during any terminal initialisation:
*
* 1. If the reset logic is enabled at all, this is an immediate no.
*
* 2. If $TERM is set to anything other than "dumb", it's a yes.
*/
if (!c->tty_reset)
return false;
return !streq_ptr(strv_env_get(c->environment, "TERM"), "dumb");
}
static void strv_fprintf(FILE *f, char **l) {
assert(f);
@@ -1610,7 +1649,7 @@ void exec_context_free_log_extra_fields(ExecContext *c) {
c->n_log_extra_fields = 0;
}
void exec_context_revert_tty(ExecContext *c) {
void exec_context_revert_tty(ExecContext *c, sd_id128_t invocation_id) {
_cleanup_close_ int fd = -EBADF;
const char *path;
struct stat st;
@@ -1619,7 +1658,7 @@ void exec_context_revert_tty(ExecContext *c) {
assert(c);
/* First, reset the TTY (possibly kicking everybody else from the TTY) */
exec_context_tty_reset(c, /* parameters= */ NULL);
exec_context_tty_reset(c, /* parameters= */ NULL, invocation_id);
/* And then undo what chown_terminal() did earlier. Note that we only do this if we have a path
* configured. If the TTY was passed to us as file descriptor we assume the TTY is opened and managed
+3 -2
View File
@@ -530,6 +530,7 @@ const char* exec_context_fdname(const ExecContext *c, int fd_index);
bool exec_context_may_touch_console(const ExecContext *c);
bool exec_context_maintains_privileges(const ExecContext *c);
bool exec_context_shall_ansi_seq_reset(const ExecContext *c);
int exec_context_get_effective_ioprio(const ExecContext *c);
bool exec_context_get_effective_mount_apivfs(const ExecContext *c);
@@ -537,14 +538,14 @@ bool exec_context_get_effective_bind_log_sockets(const ExecContext *c);
void exec_context_free_log_extra_fields(ExecContext *c);
void exec_context_revert_tty(ExecContext *c);
void exec_context_revert_tty(ExecContext *c, sd_id128_t invocation_id);
int exec_context_get_clean_directories(ExecContext *c, char **prefix, ExecCleanMask mask, char ***ret);
int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret);
const char* exec_context_tty_path(const ExecContext *context);
int exec_context_apply_tty_size(const ExecContext *context, int input_fd, int output_fd, const char *tty_path);
void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p);
void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p, sd_id128_t invocation_id);
uint64_t exec_context_get_rlimit(const ExecContext *c, const char *name);
int exec_context_get_oom_score_adjust(const ExecContext *c);
+39
View File
@@ -45,6 +45,7 @@
#include "efivars.h"
#include "emergency-action.h"
#include "env-util.h"
#include "escape.h"
#include "exit-status.h"
#include "fd-util.h"
#include "fdset.h"
@@ -57,6 +58,7 @@
#include "ima-setup.h"
#include "import-creds.h"
#include "initrd-util.h"
#include "io-util.h"
#include "ipe-setup.h"
#include "killall.h"
#include "kmod-setup.h"
@@ -73,6 +75,7 @@
#include "mount-setup.h"
#include "mount-util.h"
#include "os-util.h"
#include "osc-context.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
@@ -2382,6 +2385,38 @@ static void log_execution_mode(bool *ret_first_boot) {
*ret_first_boot = first_boot;
}
static int write_boot_or_shutdown_osc(const char *type) {
int r;
assert(STRPTR_IN_SET(type, "boot", "shutdown"));
if (getenv_terminal_is_dumb())
return 0;
_cleanup_close_ int fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return log_debug_errno(fd, "Failed to open /dev/console to print %s OSC, ignoring: %m", type);
_cleanup_free_ char *seq = NULL;
if (streq(type, "boot"))
r = osc_context_open_boot(&seq);
else
r = osc_context_close(SD_ID128_ALLF, &seq);
if (r < 0)
return log_debug_errno(r, "Failed to acquire %s OSC sequence, ignoring: %m", type);
r = loop_write(fd, seq, SIZE_MAX);
if (r < 0)
return log_debug_errno(r, "Failed to write %s OSC sequence, ignoring: %m", type);
if (DEBUG_LOGGING) {
_cleanup_free_ char *h = cescape(seq);
log_debug("OSC sequence for %s successfully written: %s", type, strna(h));
}
return 0;
}
static int initialize_runtime(
bool skip_setup,
bool first_boot,
@@ -2440,6 +2475,8 @@ static int initialize_runtime(
write_container_id();
(void) write_boot_or_shutdown_osc("boot");
/* Copy os-release to the propagate directory, so that we update it for services running
* under RootDirectory=/RootImage= when we do a soft reboot. */
r = setup_os_release(RUNTIME_SCOPE_SYSTEM);
@@ -3422,6 +3459,8 @@ finish:
}
#endif
(void) write_boot_or_shutdown_osc("shutdown");
if (r < 0)
(void) sd_notifyf(/* unset_environment= */ false,
"ERRNO=%i", -r);
+1 -1
View File
@@ -2178,7 +2178,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
(void) unlink(s->pid_file);
/* Reset TTY ownership if necessary */
exec_context_revert_tty(&s->exec_context);
exec_context_revert_tty(&s->exec_context, UNIT(s)->invocation_id);
}
static void service_enter_stop_post(Service *s, ServiceResult f) {
+1 -1
View File
@@ -121,7 +121,7 @@ static void print_welcome(int rfd) {
pn = os_release_pretty_name(pretty_name, os_name);
ac = isempty(ansi_color) ? "0" : ansi_color;
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* switch_to_text= */ false);
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
if (colors_enabled())
printf("\nWelcome to your new installation of \x1B[%sm%s\x1B[0m!\n", ac, pn);
+1 -1
View File
@@ -2515,7 +2515,7 @@ static int create_interactively(void) {
return 0;
}
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* switch_to_text= */ false);
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
for (;;) {
username = mfree(username);
+142 -7
View File
@@ -39,11 +39,13 @@
#include "format-util.h"
#include "fs-util.h"
#include "hostname-util.h"
#include "io-util.h"
#include "json-util.h"
#include "locale-util.h"
#include "login-util.h"
#include "macro.h"
#include "missing_syscall.h"
#include "osc-context.h"
#include "pam-util.h"
#include "parse-util.h"
#include "path-util.h"
@@ -1081,6 +1083,7 @@ static int register_session(
UserRecord *ur,
bool debug,
char **ret_seat,
char **ret_type,
char **ret_runtime_dir) {
int r;
@@ -1089,19 +1092,20 @@ static int register_session(
assert(c);
assert(ur);
assert(ret_seat);
assert(ret_type);
assert(ret_runtime_dir);
/* We don't register session class none with logind */
if (streq(c->class, "none")) {
pam_debug_syslog(handle, debug, "Skipping logind registration for session class none.");
*ret_seat = *ret_runtime_dir = NULL;
*ret_seat = *ret_type = *ret_runtime_dir = NULL;
return PAM_SUCCESS;
}
/* Make most of this a NOP on non-logind systems */
if (!logind_running()) {
pam_debug_syslog(handle, debug, "Skipping logind registration as logind is not running.");
*ret_seat = *ret_runtime_dir = NULL;
*ret_seat = *ret_type = *ret_runtime_dir = NULL;
return PAM_SUCCESS;
}
@@ -1174,7 +1178,7 @@ static int register_session(
if (streq_ptr(error_id, "io.systemd.Login.AlreadySessionMember")) {
/* We are already in a session, don't do anything */
pam_debug_syslog(handle, debug, "Not creating session: %s", error_id);
*ret_seat = *ret_runtime_dir = NULL;
*ret_seat = *ret_type= *ret_runtime_dir = NULL;
return PAM_SUCCESS;
}
if (error_id)
@@ -1271,7 +1275,7 @@ static int register_session(
/* We are already in a session, don't do anything */
pam_debug_syslog(handle, debug,
"Not creating session: %s", bus_error_message(&error, r));
*ret_seat = *ret_runtime_dir = NULL;
*ret_seat = *ret_type = *ret_runtime_dir = NULL;
return PAM_SUCCESS;
}
@@ -1311,6 +1315,10 @@ static int register_session(
* somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this
* data is inherited into the session processes, and programs can rely on them to be initialized. */
_cleanup_free_ char *real_type = strdup(c->type); /* make copy because this might point to env block, which we are going to update shortly */
if (!real_type)
return pam_log_oom(handle);
r = update_environment(handle, "XDG_SESSION_TYPE", c->type);
if (r != PAM_SUCCESS)
return r;
@@ -1367,6 +1375,7 @@ static int register_session(
c->vtnr = real_vtnr;
c->seat = *ret_seat = TAKE_PTR(rs);
c->type = *ret_type = TAKE_PTR(real_type);
*ret_runtime_dir = TAKE_PTR(rt);
return PAM_SUCCESS;
@@ -1574,6 +1583,126 @@ static int setup_environment(
return export_legacy_dbus_address(handle, runtime_directory);
}
static int open_osc_context(pam_handle_t *handle, const char *session_type, UserRecord *ur) {
int r;
assert(handle);
assert(ur);
/* If this is a TTY session, then output the session start OSC sequence */
if (!streq_ptr(session_type, "tty"))
return PAM_SUCCESS;
const char *e = pam_getenv(handle, "TERM");
if (!e)
e = getenv("TERM");
if (streq_ptr(e, "dumb"))
return PAM_SUCCESS;
/* NB: we output directly to stdout, instead of going via pam_info() or so, because that's too
* high-level for us, as it suffixes the output with a newline, expecting a full blown text message
* as prompt string, not just an ANSI sequence. Note that PAM's conv_misc() actually goes to stdout
* anyway, hence let's do so here too, but only after careful validation. */
if (!isatty(STDOUT_FILENO))
return PAM_SUCCESS;
/* Keep a reference to the TTY we are operating on, so that we can issue the OSC close sequence also
* if the TTY is already closed. We use an O_PATH reference here, rather than a properly opened fd,
* so that we don't delay tty hang-up. */
_cleanup_close_ int tty_opath_fd = fd_reopen(STDOUT_FILENO, O_PATH|O_CLOEXEC);
if (tty_opath_fd < 0)
pam_syslog_errno(handle, LOG_DEBUG, tty_opath_fd, "Failed to pin TTY, ignoring: %m");
else
tty_opath_fd = fd_move_above_stdio(tty_opath_fd);
_cleanup_free_ char *osc = NULL;
sd_id128_t osc_id;
r = osc_context_open_session(
ur->user_name,
pam_getenv(handle, "XDG_SESSION_ID"),
&osc,
&osc_id);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to prepare OSC sequence: %m");
r = loop_write(STDOUT_FILENO, osc, SIZE_MAX);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to write OSC sequence to TTY: %m");
/* Remember the OSC context id, so that we can close it cleanly later */
_cleanup_free_ sd_id128_t *osc_id_copy = newdup(sd_id128_t, &osc_id, 1);
if (!osc_id_copy)
return pam_log_oom(handle);
r = pam_set_data(handle, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM OSC sequence ID data: @PAMERR@");
TAKE_PTR(osc_id_copy);
if (tty_opath_fd >= 0) {
r = pam_set_data(handle, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM OSC sequence fd data: @PAMERR@");
TAKE_FD(tty_opath_fd);
}
return PAM_SUCCESS;
}
static int close_osc_context(pam_handle_t *handle) {
int r;
assert(handle);
const void *p;
int tty_opath_fd = -EBADF;
r = pam_get_data(handle, "systemd.osc-context-fd", &p);
if (r == PAM_SUCCESS)
tty_opath_fd = PTR_TO_FD(p);
else if (r != PAM_NO_MODULE_DATA)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM OSC context fd: @PAMERR@");
if (tty_opath_fd < 0)
return PAM_SUCCESS;
const sd_id128_t *osc_id = NULL;
r = pam_get_data(handle, "systemd.osc-context-id", (const void**) &osc_id);
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM OSC context id data: @PAMERR@");
if (!osc_id)
return PAM_SUCCESS;
/* Now open the original TTY again, so that we can write on it */
_cleanup_close_ int fd = fd_reopen(tty_opath_fd, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (fd < 0) {
pam_syslog_errno(handle, LOG_DEBUG, fd, "Failed to reopen TTY, ignoring: %m");
return PAM_SUCCESS;
}
/* /bin/login calls us with fds 0, 1, 2 closed, which is just weird. Let's step outside of that
* range, just in case pam_syslog() or so logs to stderr */
fd = fd_move_above_stdio(fd);
/* Safety check, let's verify this is a valid TTY we just opened */
if (!isatty(fd))
return PAM_SUCCESS;
_cleanup_free_ char *osc = NULL;
r = osc_context_close(*osc_id, &osc);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to prepare OSC sequence: %m");
r = loop_write(fd, osc, SIZE_MAX);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to write OSC sequence to TTY: %m");
return PAM_SUCCESS;
}
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle,
int flags,
@@ -1637,8 +1766,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
session_context_mangle(handle, &c, ur, debug);
_cleanup_free_ char *seat_buffer = NULL, *runtime_dir = NULL;
r = register_session(handle, &c, ur, debug, &seat_buffer, &runtime_dir);
_cleanup_free_ char *seat_buffer = NULL, *type_buffer = NULL, *runtime_dir = NULL;
r = register_session(handle, &c, ur, debug, &seat_buffer, &type_buffer, &runtime_dir);
if (r != PAM_SUCCESS)
return r;
@@ -1653,7 +1782,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (default_capability_ambient_set == UINT64_MAX)
default_capability_ambient_set = pick_default_capability_ambient_set(ur, c.service, c.seat);
return apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
r = apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
if (r != PAM_SUCCESS)
return r;
return open_osc_context(handle, c.type, ur);
}
_public_ PAM_EXTERN int pam_sm_close_session(
@@ -1690,6 +1823,8 @@ _public_ PAM_EXTERN int pam_sm_close_session(
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to get PAM systemd.existing data: @PAMERR@");
(void) close_osc_context(handle);
id = pam_getenv(handle, "XDG_SESSION_ID");
if (id && !existing) {
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
+8
View File
@@ -46,6 +46,7 @@
#include "main-func.h"
#include "mkdir.h"
#include "nulstr-util.h"
#include "osc-context.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
@@ -1222,6 +1223,13 @@ static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, i
log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name);
}
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
if (!terminal_is_dumb()) {
r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id);
if (r < 0)
return r;
}
r = sd_event_set_signal_exit(event, true);
if (r < 0)
return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m");
+11 -3
View File
@@ -85,6 +85,7 @@
#include "nsresource.h"
#include "nulstr-util.h"
#include "os-util.h"
#include "osc-context.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
@@ -3581,9 +3582,9 @@ static int inner_child(
/* LXC sets container=lxc, so follow the scheme here */
envp[n_env++] = strjoina("container=", arg_container_service_name);
envp[n_env] = strv_find_prefix(environ, "TERM=");
if (envp[n_env])
n_env++;
/* Propagate $TERM unless we are invoked in pipe mode and stdin/stdout/stderr don't refer to a TTY */
const char *term = (arg_console_mode != CONSOLE_PIPE || on_tty()) ? strv_find_prefix(environ, "TERM=") : NULL;
envp[n_env++] = (char*) (term ?: "TERM=dumb");
if (home || !uid_is_valid(arg_uid) || arg_uid == 0)
if (asprintf(envp + n_env++, "HOME=%s", home ?: "/root") < 0)
@@ -5645,6 +5646,13 @@ static int run_container(
(void) expose_port_execute(rtnl, &expose_args->fw_ctx, arg_expose_ports, AF_INET6, &expose_args->address6);
}
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
if (IN_SET(arg_console_mode, CONSOLE_INTERACTIVE, CONSOLE_READ_ONLY) && !terminal_is_dumb()) {
r = osc_context_open_container(arg_machine, /* ret_seq= */ NULL, &osc_context_id);
if (r < 0)
return r;
}
if (arg_console_mode != CONSOLE_PIPE) {
_cleanup_close_ int fd = -EBADF;
PTYForwardFlags flags = 0;
+21 -5
View File
@@ -31,6 +31,7 @@
#include "fs-util.h"
#include "hostname-util.h"
#include "main-func.h"
#include "osc-context.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-util.h"
@@ -1232,7 +1233,7 @@ static int transient_kill_set_properties(sd_bus_message *m) {
}
static int transient_service_set_properties(sd_bus_message *m, const char *pty_path, int pty_fd) {
bool send_term = false;
int send_term = false; /* tri-state */
int r;
/* We disable environment expansion on the server side via ExecStartEx=:.
@@ -1311,14 +1312,22 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
if (r < 0)
return bus_log_create_error(r);
send_term = isatty_safe(STDIN_FILENO) || isatty_safe(STDOUT_FILENO) || isatty_safe(STDERR_FILENO);
send_term = -1;
}
if (send_term) {
if (send_term != 0) {
const char *e;
e = getenv("TERM");
if (e) {
/* Propagate $TERM only if we are actually connected to a TTY */
if (isatty_safe(STDIN_FILENO) || isatty_safe(STDOUT_FILENO) || isatty_safe(STDERR_FILENO)) {
e = getenv("TERM");
send_term = true;
} else
/* If we are not connected to any TTY ourselves, then send TERM=dumb, but only if we
* really need to (because we actually allocated a TTY for the service) */
e = "dumb";
if (send_term > 0) {
_cleanup_free_ char *n = NULL;
n = strjoin("TERM=", e);
@@ -2194,7 +2203,14 @@ static int start_transient_service(sd_bus *bus) {
if (!c.bus_path)
return log_oom();
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
if (pty_fd >= 0) {
if (!terminal_is_dumb() && arg_exec_user) {
r = osc_context_open_chpriv(arg_exec_user, /* ret_seq= */ NULL, &osc_context_id);
if (r < 0)
return r;
}
(void) sd_event_set_signal_exit(c.event, true);
if (!arg_quiet)
+1
View File
@@ -132,6 +132,7 @@ shared_sources = files(
'numa-util.c',
'open-file.c',
'openssl-util.c',
'osc-context.c',
'output-mode.c',
'pager.c',
'parse-argument.c',
+342
View File
@@ -0,0 +1,342 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/auxv.h>
#include "escape.h"
#include "hostname-util.h"
#include "id128-util.h"
#include "osc-context.h"
#include "pidfd-util.h"
#include "process-util.h"
#include "string-util.h"
#include "terminal-util.h"
#include "user-util.h"
/* This currently generates open sequences for OSC 3008 types "boot", "container", "vm", "elevate", "chpriv",
* "subcontext", "session", "sevice".
*
* NB: Not generated by systemd: "remote" (would have to be generated from the SSH client), "app". */
static int strextend_escaped(char **s, const char *prefix, const char *value) {
assert(s);
assert(value);
if (!strextend(s, prefix))
return -ENOMEM;
_cleanup_free_ char *e = xescape(value, ";\\");
if (!e)
return -ENOMEM;
if (!strextend(s, e))
return -ENOMEM;
return 0;
}
static int osc_append_identity(char **s) {
int r;
assert(s);
_cleanup_free_ char *u = getusername_malloc();
if (u) {
r = strextend_escaped(s, ";user=", u);
if (r < 0)
return r;
}
_cleanup_free_ char *h = gethostname_malloc();
if (h) {
r = strextend_escaped(s, ";hostname=", h);
if (r < 0)
return r;
}
sd_id128_t id;
if (sd_id128_get_machine(&id) >= 0) {
r = strextendf(s, ";machineid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
if (r < 0)
return r;
}
if (sd_id128_get_boot(&id) >= 0) {
r = strextendf(s, ";bootid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
if (r < 0)
return r;
}
r = strextendf(s, ";pid=" PID_FMT, getpid_cached());
if (r < 0)
return r;
uint64_t pidfdid;
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
r = strextendf(s, ";pidfdid=%" PRIu64, pidfdid);
if (r < 0)
return r;
}
r = strextend_escaped(s, ";comm=", program_invocation_short_name);
if (r < 0)
return r;
return 0;
}
static void osc_context_default_id(sd_id128_t *ret_id) {
/* Usually we only want one context ID per tool. Since we don't want to store the ID let's just hash
* one from process credentials */
struct {
const char label[32];
uint64_t pidfdid;
uint8_t auxval[16];
pid_t pid;
} data = {
.label = "systemd context hash bytes v1",
.pid = getpid_cached(),
};
assert(ret_id);
memcpy(data.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(data.auxval));
(void) pidfd_get_inode_id_self_cached(&data.pidfdid);
*ret_id = id128_digest(&data, sizeof(data));
}
static int osc_context_intro_raw(sd_id128_t id, char **ret_seq) {
int r;
assert(ret_seq);
_cleanup_free_ char *seq = NULL;
if (asprintf(&seq, ANSI_OSC "3008;start=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)) < 0)
return -ENOMEM;
r = osc_append_identity(&seq);
if (r < 0)
return r;
*ret_seq = TAKE_PTR(seq);
return 0;
}
static int osc_context_intro(char **ret_seq, sd_id128_t *ret_context_id) {
int r;
assert(ret_seq);
/* If the user passed us a buffer for the context ID generate a randomized one, since we have a place
* to store it. The user should pass the ID back to osc_context_close() later on. If the user did not
* pass us a buffer, we'll use a session ID hashed from process properties that remain stable as long
* our process exists. It hence also remains stable across reexec and similar. */
sd_id128_t id;
if (ret_context_id) {
r = sd_id128_randomize(&id);
if (r < 0)
return r;
} else
osc_context_default_id(&id);
_cleanup_free_ char *seq = NULL;
r = osc_context_intro_raw(id, &seq);
if (r < 0)
return r;
if (ret_context_id)
*ret_context_id = id;
*ret_seq = TAKE_PTR(seq);
return 0;
}
static int osc_context_outro(char *_seq, sd_id128_t id, char **ret_seq, sd_id128_t *ret_context_id) {
_cleanup_free_ char *seq = TAKE_PTR(_seq); /* We take possession of the string no matter what */
if (ret_seq)
*ret_seq = TAKE_PTR(seq);
else {
fputs(seq, stdout);
fflush(stdout);
}
if (ret_context_id)
*ret_context_id = id;
return 0;
}
int osc_context_open_boot(char **ret_seq) {
int r;
_cleanup_free_ char *seq = NULL;
r = osc_context_intro(&seq, /* ret_context_id= */ NULL);
if (r < 0)
return r;
if (!strextend(&seq, ";type=boot" ANSI_ST))
return -ENOMEM;
return osc_context_outro(TAKE_PTR(seq), /* context_id= */ SD_ID128_NULL, ret_seq, /* ret_context_id= */ NULL);
}
int osc_context_open_container(const char *name, char **ret_seq, sd_id128_t *ret_context_id) {
int r;
_cleanup_free_ char *seq = NULL;
sd_id128_t id = SD_ID128_NULL;
r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
if (r < 0)
return r;
if (name) {
r = strextend_escaped(&seq, ";container=", name);
if (r < 0)
return r;
}
if (!strextend(&seq, ";type=container" ANSI_ST))
return -ENOMEM;
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
}
int osc_context_open_vm(const char *name, char **ret_seq, sd_id128_t *ret_context_id) {
int r;
assert(name);
_cleanup_free_ char *seq = NULL;
sd_id128_t id = SD_ID128_NULL;
r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
if (r < 0)
return r;
r = strextend_escaped(&seq, ";vm=", name);
if (r < 0)
return r;
if (!strextend(&seq, ";type=vm" ANSI_ST))
return -ENOMEM;
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
}
int osc_context_open_chpriv(const char *target_user, char **ret_seq, sd_id128_t *ret_context_id) {
int r;
assert(target_user);
_cleanup_free_ char *seq = NULL;
sd_id128_t id = SD_ID128_NULL;
r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
if (r < 0)
return r;
if (is_this_me(target_user) > 0) {
if (!strextend(&seq, ";type=subcontext" ANSI_ST))
return -ENOMEM;
} else if (STR_IN_SET(target_user, "root", "0")) {
if (!strextend(&seq, ";type=elevate" ANSI_ST))
return -ENOMEM;
} else {
r = strextend_escaped(&seq, ";targetuser=", target_user);
if (r < 0)
return r;
if (!strextend(&seq, ";type=chpriv" ANSI_ST))
return -ENOMEM;
}
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
}
int osc_context_open_session(const char *user, const char *session_id, char **ret_seq, sd_id128_t *ret_context_id) {
int r;
_cleanup_free_ char *seq = NULL;
sd_id128_t id = SD_ID128_NULL;
r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
if (r < 0)
return r;
if (user) {
r = strextend_escaped(&seq, ";targetuser=", user);
if (r < 0)
return r;
}
if (session_id) {
r = strextend_escaped(&seq, ";sessionid=", session_id);
if (r < 0)
return r;
}
if (!strextend(&seq, ";type=session" ANSI_ST))
return -ENOMEM;
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
}
int osc_context_open_service(const char *unit, sd_id128_t invocation_id, char **ret_seq) {
int r;
sd_id128_t id = SD_ID128_NULL;
r = osc_context_id_from_invocation_id(invocation_id, &id);
if (r < 0)
return r;
_cleanup_free_ char *seq = NULL;
r = osc_context_intro_raw(id, &seq);
if (r < 0)
return r;
if (unit) {
r = strextend_escaped(&seq, ";servicename=", unit);
if (r < 0)
return r;
}
r = strextendf(&seq, ";invocationid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(invocation_id));
if (r < 0)
return r;
if (!strextend(&seq, ";type=service" ANSI_ST))
return -ENOMEM;
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, /* ret_context_id= */ NULL);
}
int osc_context_close(sd_id128_t id, char **ret_seq) {
if (sd_id128_is_null(id)) {
/* nil uuid: no session opened */
if (ret_seq)
*ret_seq = NULL;
return 0;
}
if (sd_id128_is_allf(id)) /* max uuid: default session opened */
osc_context_default_id(&id);
_cleanup_free_ char *seq = NULL;
if (asprintf(&seq, ANSI_OSC "3008;end=" SD_ID128_FORMAT_STR ANSI_ST, SD_ID128_FORMAT_VAL(id)) < 0)
return -ENOMEM;
return osc_context_outro(TAKE_PTR(seq), SD_ID128_NULL, ret_seq, /* ret_context_id= */ NULL);
}
int osc_context_id_from_invocation_id(sd_id128_t id, sd_id128_t *ret) {
static const sd_id128_t app_id = SD_ID128_MAKE(5d,63,a5,8d,96,fd,45,d0,a0,f0,63,50,fc,d8,9a,cd);
assert(ret);
/* Hash the context id from the invocation id, so that we don't have to store the context id (as long
* as we have the invocation ID). We don't use the invocation ID literally in order not to make it too
* easy to cancel our service context because the invocation ID is reported literally via the
* $INVOCATION_ID env var. */
return sd_id128_get_app_specific(id, app_id, ret);
}
+20
View File
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-id128.h"
#include "macro.h"
int osc_context_open_boot(char **ret_seq);
int osc_context_open_container(const char *name, char **ret_seq, sd_id128_t *ret_context_id);
int osc_context_open_vm(const char *name, char **ret_seq, sd_id128_t *ret_context_id);
int osc_context_open_chpriv(const char *target_user, char **ret_seq, sd_id128_t *ret_context_id);
int osc_context_open_session(const char *user, const char *session_id, char **ret_seq, sd_id128_t *ret_context_id);
int osc_context_open_service(const char *unit, sd_id128_t invocation_id, char **ret_seq);
int osc_context_close(sd_id128_t id, char **ret_seq);
static inline void osc_context_closep(sd_id128_t *context_id) {
(void) osc_context_close(*ASSERT_PTR(context_id), NULL);
}
int osc_context_id_from_invocation_id(sd_id128_t id, sd_id128_t *ret);
+15
View File
@@ -7,6 +7,7 @@
#include "alloc-util.h"
#include "bus-internal.h"
#include "errno-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "macro.h"
#include "pam-util.h"
@@ -249,6 +250,20 @@ void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status) {
free(data);
}
void pam_cleanup_close(pam_handle_t *handle, void *data, int error_status) {
/* A generic destructor for pam_set_data() that just closes the specified fd.
*
* As per pam_set_data() docs: the PAM_DATA_SILENT indicates whether we are called in the forked off
* payload child of the new session. However, all file descriptors are most likely already closed
* there (that's what /bin/login does after all), hence let's simply turn this into a NOP in the
* child, and only close the fd in the parent. */
if (FLAGS_SET(error_status, PAM_DATA_SILENT))
return;
safe_close(PTR_TO_FD(data));
}
int pam_get_item_many_internal(pam_handle_t *handle, ...) {
va_list ap;
int r;
+1
View File
@@ -42,6 +42,7 @@ int pam_release_bus_connection(pam_handle_t *handle, const char *module_name);
int pam_get_bus_data(pam_handle_t *handle, const char *module_name, PamBusData **ret);
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);
void pam_cleanup_close(pam_handle_t *handle, void *data, int error_status);
int pam_get_item_many_internal(pam_handle_t *handle, ...);
#define pam_get_item_many(handle, ...) pam_get_item_many_internal(handle, __VA_ARGS__, -1)
+1
View File
@@ -137,6 +137,7 @@ simple_tests += files(
'test-open-file.c',
'test-ordered-set.c',
'test-os-util.c',
'test-osc-context.c',
'test-parse-argument.c',
'test-parse-helpers.c',
'test-path-lookup.c',
+89
View File
@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "hexdecoct.h"
#include "osc-context.h"
#include "tests.h"
#include "user-util.h"
TEST(osc) {
_cleanup_free_ char *seq = NULL;
log_info("boot");
ASSERT_OK(osc_context_open_boot(&seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
ASSERT_OK(osc_context_close(SD_ID128_ALLF, &seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
log_info("container");
sd_id128_t id;
ASSERT_OK(osc_context_open_container("foobar", &seq, &id));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
ASSERT_OK(osc_context_close(id, &seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
log_info("vm");
ASSERT_OK(osc_context_open_vm("foobar", &seq, &id));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
ASSERT_OK(osc_context_close(id, &seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
log_info("chpriv → root");
ASSERT_OK(osc_context_open_chpriv("root", &seq, &id));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
ASSERT_OK(osc_context_close(id, &seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
log_info("chpriv → userxyz");
ASSERT_OK(osc_context_open_chpriv("userxyz", &seq, &id));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
ASSERT_OK(osc_context_close(id, &seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
log_info("chpriv → self");
_cleanup_free_ char *self = ASSERT_PTR(getusername_malloc());
ASSERT_OK(osc_context_open_chpriv(self, &seq, &id));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
ASSERT_OK(osc_context_close(id, &seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
log_info("session");
ASSERT_OK(osc_context_open_session("foobaruser", "session1", &seq, &id));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
ASSERT_OK(osc_context_close(id, &seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
log_info("service");
sd_id128_t invocation_id;
ASSERT_OK(sd_id128_randomize(&invocation_id));
ASSERT_OK(osc_context_open_service("getty@tty1.service", invocation_id, &seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
ASSERT_OK(osc_context_id_from_invocation_id(invocation_id, &id));
ASSERT_OK(osc_context_close(id, &seq));
hexdump(/* f = */ NULL, seq, SIZE_MAX);
seq = mfree(seq);
}
DEFINE_TEST_MAIN(LOG_INFO);
+1 -1
View File
@@ -290,7 +290,7 @@ TEST(get_color_mode) {
TEST(terminal_reset_defensive) {
int r;
r = terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false);
r = terminal_reset_defensive(STDOUT_FILENO, /* flags= */ 0);
if (r < 0)
log_notice_errno(r, "Failed to reset terminal: %m");
}
@@ -145,7 +145,7 @@ static int agent_ask_password_tty(
if (tty_fd < 0)
return log_error_errno(tty_fd, "Failed to acquire %s: %m", con);
(void) terminal_reset_defensive_locked(tty_fd, /* switch_to_text= */ true);
(void) terminal_reset_defensive_locked(tty_fd, TERMINAL_RESET_SWITCH_TO_TEXT);
log_info("Starting password query on %s.", con);
}
+8
View File
@@ -48,6 +48,7 @@
#include "mkdir.h"
#include "namespace-util.h"
#include "netif-util.h"
#include "osc-context.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
@@ -2249,8 +2250,15 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
/* Exit when the child exits */
(void) event_add_child_pidref(event, NULL, &child_pidref, WEXITED, on_child_exit, NULL);
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
_cleanup_(pty_forward_freep) PTYForward *forward = NULL;
if (master >= 0) {
if (!terminal_is_dumb()) {
r = osc_context_open_vm(arg_machine, /* ret_seq= */ NULL, &osc_context_id);
if (r < 0)
return r;
}
r = pty_forward_new(event, master, ptyfwd_flags, &forward);
if (r < 0)
return log_error_errno(r, "Failed to create PTY forwarder: %m");
+1 -1
View File
@@ -399,7 +399,7 @@ grep -q "BAR" /tmp/none-existent-file
kill "$PID"
# Test varlinkctl's --exec fd passing logic properly
assert_eq "$(varlinkctl --exec call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/bash", "args": ["/bin/bash", "-c", "echo $((7 + 8))"]}' -- bash -c 'read -r -N 2 x <&3 ; echo "$x"')" 15
assert_eq "$(varlinkctl --exec call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/bash", "args": ["/bin/bash", "-c", "echo $((7 + 8))"], "environment": ["TERM=dumb"]}' -- bash -c 'read -r -N 2 x <&3 ; echo "$x"')" 15
# test io.systemd.Machine.MapFrom
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapFrom '{"name": "long-running", "uid":0, "gid": 0}'
+12
View File
@@ -0,0 +1,12 @@
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
# See tmpfiles.d(5) for details.
{% if LINK_OSC_CONTEXT_DROPIN %}
L$ {{SHELLPROFILEDIR}}/80-systemd-osc-context.sh - - - - {{LIBEXECDIR}}/profile.d/80-systemd-osc-context.sh
{% endif %}
+1
View File
@@ -31,6 +31,7 @@ endforeach
in_files = [
['20-systemd-shell-extra.conf', 'LINK_SHELL_EXTRA_DROPIN'],
['20-systemd-osc-context.conf', 'LINK_OSC_CONTEXT_DROPIN'],
['20-systemd-ssh-generator.conf', 'ENABLE_SSH_PROXY_CONFIG'],
['20-systemd-stub.conf', 'ENABLE_EFI'],
['20-systemd-userdb.conf', 'ENABLE_SSH_USERDB_CONFIG'],