1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-26 14:04:03 +03:00

logind: introduce CreateSessionWithPIDFD()

This new D-Bus API uses pidfd to refer to the session leader. Also,
pam_systemd will try to make use of it when pidfd support is available.
This commit is contained in:
Michal Sekletar 2023-10-06 12:26:44 +02:00 committed by Luca Boccassi
parent c895d047e3
commit 76f2191d8e
6 changed files with 410 additions and 98 deletions

View File

@ -80,6 +80,30 @@ node /org/freedesktop/login1 {
out u vtnr,
out b existing);
@org.freedesktop.systemd1.Privileged("true")
CreateSessionWithPIDFD(in u uid,
in h pidfd,
in s service,
in s type,
in s class,
in s desktop,
in s seat_id,
in u vtnr,
in s tty,
in s display,
in b remote,
in s remote_user,
in s remote_host,
in t flags,
in a(sv) properties,
out s session_id,
out o object_path,
out s runtime_path,
out h fifo_fd,
out u uid,
out s seat_id,
out u vtnr,
out b existing);
@org.freedesktop.systemd1.Privileged("true")
ReleaseSession(in s session_id);
ActivateSession(in s session_id);
ActivateSessionOnSeat(in s session_id,
@ -294,6 +318,8 @@ node /org/freedesktop/login1 {
<variablelist class="dbus-method" generated="True" extra-ref="CreateSession()"/>
<variablelist class="dbus-method" generated="True" extra-ref="CreateSessionWithPIDFD()"/>
<variablelist class="dbus-method" generated="True" extra-ref="ReleaseSession()"/>
<variablelist class="dbus-method" generated="True" extra-ref="ActivateSession()"/>
@ -525,10 +551,10 @@ node /org/freedesktop/login1 {
structures consisting of <varname>what</varname>, <varname>who</varname>, <varname>why</varname>,
<varname>mode</varname>, <varname>uid</varname> (user ID), and <varname>pid</varname> (process ID).</para>
<para><function>CreateSession()</function> and <function>ReleaseSession()</function> may be used to
open or close login sessions. These calls should <emphasis>never</emphasis> be invoked directly by
clients. Creating/closing sessions is exclusively the job of PAM and its
<citerefentry><refentrytitle>pam_systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
<para><function>CreateSession()</function>, <function>CreateSessionWithPIDFD()</function>, and
<function>ReleaseSession()</function> may be used to open or close login sessions. These calls should
<emphasis>never</emphasis> be invoked directly by clients. Creating/closing sessions is exclusively the job
of PAM and its <citerefentry><refentrytitle>pam_systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
module.</para>
<para><function>ActivateSession()</function> brings the session with the specified ID into the
@ -1520,7 +1546,8 @@ node /org/freedesktop/login1/session/1 {
<varname>HandleSuspendKeyLongPress</varname>, and
<varname>HandleHibernateKeyLongPress</varname> were added in version 251.</para>
<para><varname>StopIdleSessionUSec</varname> was added in version 252.</para>
<para><function>PrepareForShutdownWithMetadata</function> was added in version 255.</para>
<para><function>PrepareForShutdownWithMetadata</function> and
<function>CreateSessionWithPIDFD()</function> were added in version 255.</para>
</refsect2>
<refsect2>
<title>Session Objects</title>

View File

@ -677,36 +677,73 @@ static int method_list_inhibitors(sd_bus_message *message, void *userdata, sd_bu
return sd_bus_send(NULL, reply, NULL);
}
static int method_create_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
const char *service, *type, *class, *cseat, *tty, *display, *remote_user, *remote_host, *desktop;
static int create_session(
sd_bus_message *message,
void *userdata,
sd_bus_error *error,
uid_t uid,
pid_t pid,
int pidfd,
const char *service,
const char *type,
const char *class,
const char *desktop,
const char *cseat,
uint32_t vtnr,
const char *tty,
const char *display,
int remote,
const char *remote_user,
const char *remote_host,
uint64_t flags) {
_cleanup_(pidref_done) PidRef leader = PIDREF_NULL;
Manager *m = ASSERT_PTR(userdata);
_cleanup_free_ char *id = NULL;
Session *session = NULL;
uint32_t audit_id = 0;
Manager *m = ASSERT_PTR(userdata);
User *user = NULL;
Seat *seat = NULL;
pid_t leader;
uid_t uid;
int remote;
uint32_t vtnr = 0;
SessionType t;
SessionClass c;
int r;
assert(message);
assert_cc(sizeof(pid_t) == sizeof(uint32_t));
assert_cc(sizeof(uid_t) == sizeof(uint32_t));
r = sd_bus_message_read(message, "uusssssussbss",
&uid, &leader, &service, &type, &class, &desktop, &cseat,
&vtnr, &tty, &display, &remote, &remote_user, &remote_host);
if (r < 0)
return r;
if (!uid_is_valid(uid))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid UID");
if (leader < 0 || leader == 1 || leader == getpid_cached())
if (flags != 0)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero.");
if (pidfd >= 0) {
r = pidref_set_pidfd(&leader, pidfd);
if (r < 0)
return r;
} else if (pid == 0) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
pid_t p;
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds);
if (r < 0)
return r;
r = sd_bus_creds_get_pid(creds, &p);
if (r < 0)
return r;
r = pidref_set_pid(&leader, p);
if (r < 0)
return r;
} else {
assert(pid > 0);
r = pidref_set_pid(&leader, pid);
if (r < 0)
return r;
}
if (leader.pid == 1 || leader.pid == getpid_cached())
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID");
if (isempty(type))
@ -805,21 +842,9 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
c = SESSION_USER;
}
if (leader == 0) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds);
if (r < 0)
return r;
r = sd_bus_creds_get_pid(creds, (pid_t*) &leader);
if (r < 0)
return r;
}
/* Check if we are already in a logind session. Or if we are in user@.service
* which is a special PAM session that avoids creating a logind session. */
r = manager_get_user_by_pid(m, leader, NULL);
r = manager_get_user_by_pid(m, leader.pid, NULL);
if (r < 0)
return r;
if (r > 0)
@ -848,7 +873,7 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
"Maximum number of sessions (%" PRIu64 ") reached, refusing further sessions.",
m->sessions_max);
(void) audit_session_from_pid(leader, &audit_id);
(void) audit_session_from_pid(leader.pid, &audit_id);
if (audit_session_is_valid(audit_id)) {
/* Keep our session IDs and the audit session IDs in sync */
@ -890,7 +915,7 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
goto fail;
session_set_user(session, user);
r = session_set_leader(session, leader);
r = session_set_leader_consume(session, TAKE_PIDREF(leader));
if (r < 0)
goto fail;
@ -984,6 +1009,107 @@ fail:
return r;
}
static int method_create_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
const char *service, *type, *class, *cseat, *tty, *display, *remote_user, *remote_host, *desktop;
pid_t leader;
uid_t uid;
int remote;
uint32_t vtnr = 0;
int r;
assert(message);
assert_cc(sizeof(pid_t) == sizeof(uint32_t));
assert_cc(sizeof(uid_t) == sizeof(uint32_t));
r = sd_bus_message_read(message,
"uusssssussbss",
&uid,
&leader,
&service,
&type,
&class,
&desktop,
&cseat,
&vtnr,
&tty,
&display,
&remote,
&remote_user,
&remote_host);
if (r < 0)
return r;
return create_session(
message,
userdata,
error,
uid,
leader,
/* pidfd = */ -EBADF,
service,
type,
class,
desktop,
cseat,
vtnr,
tty,
display,
remote,
remote_user,
remote_host,
/* flags = */ 0);
}
static int method_create_session_pidfd(sd_bus_message *message, void *userdata, sd_bus_error *error) {
const char *service, *type, *class, *cseat, *tty, *display, *remote_user, *remote_host, *desktop;
int leaderfd = -EBADF;
uid_t uid;
int remote;
uint32_t vtnr = 0;
uint64_t flags;
int r;
r = sd_bus_message_read(message,
"uhsssssussbsst",
&uid,
&leaderfd,
&service,
&type,
&class,
&desktop,
&cseat,
&vtnr,
&tty,
&display,
&remote,
&remote_user,
&remote_host,
&flags);
if (r < 0)
return r;
return create_session(
message,
userdata,
error,
uid,
/* pid = */ 0,
leaderfd,
service,
type,
class,
desktop,
cseat,
vtnr,
tty,
display,
remote,
remote_user,
remote_host,
flags);
}
static int method_release_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = ASSERT_PTR(userdata);
Session *session;
@ -3494,6 +3620,32 @@ static const sd_bus_vtable manager_vtable[] = {
"b", existing),
method_create_session,
0),
SD_BUS_METHOD_WITH_ARGS("CreateSessionWithPIDFD",
SD_BUS_ARGS("u", uid,
"h", pidfd,
"s", service,
"s", type,
"s", class,
"s", desktop,
"s", seat_id,
"u", vtnr,
"s", tty,
"s", display,
"b", remote,
"s", remote_user,
"s", remote_host,
"t", flags,
"a(sv)", properties),
SD_BUS_RESULT("s", session_id,
"o", object_path,
"s", runtime_path,
"h", fifo_fd,
"u", uid,
"s", seat_id,
"u", vtnr,
"b", existing),
method_create_session_pidfd,
0),
SD_BUS_METHOD_WITH_ARGS("ReleaseSession",
SD_BUS_ARGS("s", session_id),
SD_BUS_NO_RESULT,

View File

@ -810,6 +810,7 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
_cleanup_close_ int fifo_fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
assert(s);
@ -830,6 +831,10 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
if (fifo_fd < 0)
return fifo_fd;
r = session_watch_pidfd(s);
if (r < 0)
return r;
/* Update the session state file before we notify the client about the result. */
session_save(s);

View File

@ -170,22 +170,16 @@ void session_set_user(Session *s, User *u) {
user_update_last_session_timer(u);
}
int session_set_leader(Session *s, pid_t pid) {
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
int session_set_leader_consume(Session *s, PidRef _leader) {
_cleanup_(pidref_done) PidRef pidref = _leader;
int r;
assert(s);
assert(pidref_is_set(&pidref));
if (!pid_is_valid(pid))
return -EINVAL;
if (s->leader.pid == pid)
if (pidref_equal(&s->leader, &pidref))
return 0;
r = pidref_set_pid(&pidref, pid);
if (r < 0)
return r;
r = hashmap_put(s->manager->sessions_by_leader, PID_TO_PTR(pidref.pid), s);
if (r < 0)
return r;
@ -523,13 +517,13 @@ int session_load(Session *s) {
}
if (leader) {
pid_t pid;
_cleanup_(pidref_done) PidRef p = PIDREF_NULL;
r = parse_pid(leader, &pid);
r = pidref_set_pidstr(&p, leader);
if (r < 0)
log_debug_errno(r, "Failed to parse leader PID of session: %s", leader);
else {
r = session_set_leader(s, pid);
r = session_set_leader_consume(s, TAKE_PIDREF(p));
if (r < 0)
log_warning_errno(r, "Failed to set session leader PID, ignoring: %m");
}
@ -884,6 +878,7 @@ int session_stop(Session *s, bool force) {
return 0;
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
s->leader_pidfd_event_source = sd_event_source_unref(s->leader_pidfd_event_source);
if (s->seat)
seat_evict_position(s->seat, s);
@ -921,6 +916,7 @@ int session_finalize(Session *s) {
LOG_MESSAGE("Removed session %s.", s->id));
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
s->leader_pidfd_event_source = sd_event_source_unref(s->leader_pidfd_event_source);
if (s->seat)
seat_evict_position(s->seat, s);
@ -945,6 +941,8 @@ int session_finalize(Session *s) {
seat_save(s->seat);
}
pidref_done(&s->leader);
user_save(s->user);
user_send_changed(s->user, "Display", NULL);
@ -1226,6 +1224,36 @@ static void session_remove_fifo(Session *s) {
}
}
static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
Session *s = ASSERT_PTR(userdata);
assert(s->leader.fd == fd);
session_stop(s, /* force= */ false);
return 1;
}
int session_watch_pidfd(Session *s) {
int r;
assert(s);
if (s->leader.fd < 0)
return 0;
r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s);
if (r < 0)
return r;
r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT);
if (r < 0)
return r;
(void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd");
return 0;
}
bool session_may_gc(Session *s, bool drop_not_started) {
int r;
@ -1237,6 +1265,12 @@ bool session_may_gc(Session *s, bool drop_not_started) {
if (!s->user)
return true;
r = pidref_is_alive(&s->leader);
if (r < 0)
log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not.", s->leader.pid);
if (r > 0)
return false;
if (s->fifo_fd >= 0) {
if (pipe_eof(s->fifo_fd) <= 0)
return false;
@ -1282,7 +1316,7 @@ SessionState session_get_state(Session *s) {
if (s->stopping || s->timer_event_source)
return SESSION_CLOSING;
if (s->scope_job || s->fifo_fd < 0)
if (s->scope_job || (!pidref_is_set(&s->leader) && s->fifo_fd < 0))
return SESSION_OPENING;
if (session_is_active(s))

View File

@ -95,6 +95,7 @@ struct Session {
char *fifo_path;
sd_event_source *fifo_event_source;
sd_event_source *leader_pidfd_event_source;
bool idle_hint;
dual_timestamp idle_hint_timestamp;
@ -130,7 +131,7 @@ Session* session_free(Session *s);
DEFINE_TRIVIAL_CLEANUP_FUNC(Session *, session_free);
void session_set_user(Session *s, User *u);
int session_set_leader(Session *s, pid_t pid);
int session_set_leader_consume(Session *s, PidRef _leader);
bool session_may_gc(Session *s, bool drop_not_started);
void session_add_to_gc_queue(Session *s);
int session_activate(Session *s);
@ -143,6 +144,7 @@ void session_set_type(Session *s, SessionType t);
int session_set_display(Session *s, const char *display);
int session_set_tty(Session *s, const char *tty);
int session_create_fifo(Session *s);
int session_watch_pidfd(Session *s);
int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error);
int session_stop(Session *s, bool force);
int session_finalize(Session *s);

View File

@ -34,6 +34,7 @@
#include "locale-util.h"
#include "login-util.h"
#include "macro.h"
#include "missing_syscall.h"
#include "pam-util.h"
#include "parse-util.h"
#include "path-util.h"
@ -780,6 +781,101 @@ static uint64_t pick_default_capability_ambient_set(
(streq_ptr(service, "systemd-user") || !isempty(seat)) ? (UINT64_C(1) << CAP_WAKE_ALARM) : UINT64_MAX;
}
typedef struct SessionContext {
const uid_t uid;
const pid_t pid;
const char *service;
const char *type;
const char *class;
const char *desktop;
const char *seat;
const uint32_t vtnr;
const char *tty;
const char *display;
const bool remote;
const char *remote_user;
const char *remote_host;
const char *memory_max;
const char *tasks_max;
const char *cpu_weight;
const char *io_weight;
const char *runtime_max_sec;
} SessionContext;
static int create_session_message(sd_bus *bus, pam_handle_t *handle, const SessionContext *context, bool avoid_pidfd, sd_bus_message **ret) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
int r, pidfd = -EBADFD;
assert(bus);
assert(handle);
assert(context);
if (!avoid_pidfd) {
pidfd = pidfd_open(getpid_cached(), 0);
if (pidfd < 0 && !ERRNO_IS_NOT_SUPPORTED(errno))
return -errno;
}
r = bus_message_new_method_call(bus, &m, bus_login_mgr, pidfd >= 0 ? "CreateSessionWithPIDFD" : "CreateSession");
if (r < 0)
return r;
r = sd_bus_message_append(m,
pidfd >= 0 ? "uhsssssussbss" : "uusssssussbss",
(uint32_t) context->uid,
pidfd >= 0 ? pidfd : context->pid,
context->service,
context->type,
context->class,
context->desktop,
context->seat,
context->vtnr,
context->tty,
context->display,
context->remote,
context->remote_user,
context->remote_host);
if (r < 0)
return r;
if (pidfd >= 0) {
r = sd_bus_message_append(m, "t", UINT64_C(0));
if (r < 0)
return r;
}
r = sd_bus_message_open_container(m, 'a', "(sv)");
if (r < 0)
return r;
r = append_session_memory_max(handle, m, context->memory_max);
if (r != PAM_SUCCESS)
return r;
r = append_session_runtime_max_sec(handle, m, context->runtime_max_sec);
if (r != PAM_SUCCESS)
return r;
r = append_session_tasks_max(handle, m, context->tasks_max);
if (r != PAM_SUCCESS)
return r;
r = append_session_cpu_weight(handle, m, context->cpu_weight);
if (r != PAM_SUCCESS)
return r;
r = append_session_io_weight(handle, m, context->io_weight);
if (r != PAM_SUCCESS)
return r;
r = sd_bus_message_close_container(m);
if (r < 0)
return r;
*ret = TAKE_PTR(m);
return 0;
}
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle,
int flags,
@ -963,52 +1059,32 @@ _public_ PAM_EXTERN int pam_sm_open_session(
"memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s",
strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight), strna(runtime_max_sec));
r = bus_message_new_method_call(bus, &m, bus_login_mgr, "CreateSession");
if (r < 0)
return pam_bus_log_create_error(handle, r);
const SessionContext context = {
.uid = ur->uid,
.pid = 0,
.service = service,
.type = type,
.class = class,
.desktop = desktop,
.seat = seat,
.vtnr = vtnr,
.tty = tty,
.display = display,
.remote = remote,
.remote_user = remote_user,
.remote_host = remote_host,
.memory_max = memory_max,
.tasks_max = tasks_max,
.cpu_weight = cpu_weight,
.io_weight = io_weight,
.runtime_max_sec = runtime_max_sec,
};
r = sd_bus_message_append(m, "uusssssussbss",
(uint32_t) ur->uid,
0,
service,
type,
class,
desktop,
seat,
vtnr,
tty,
display,
remote,
remote_user,
remote_host);
if (r < 0)
return pam_bus_log_create_error(handle, r);
r = sd_bus_message_open_container(m, 'a', "(sv)");
if (r < 0)
return pam_bus_log_create_error(handle, r);
r = append_session_memory_max(handle, m, memory_max);
if (r != PAM_SUCCESS)
return r;
r = append_session_runtime_max_sec(handle, m, runtime_max_sec);
if (r != PAM_SUCCESS)
return r;
r = append_session_tasks_max(handle, m, tasks_max);
if (r != PAM_SUCCESS)
return r;
r = append_session_cpu_weight(handle, m, cpu_weight);
if (r != PAM_SUCCESS)
return r;
r = append_session_io_weight(handle, m, io_weight);
if (r != PAM_SUCCESS)
return r;
r = sd_bus_message_close_container(m);
r = create_session_message(bus,
handle,
&context,
false /* avoid_pidfd = */,
&m);
if (r < 0)
return pam_bus_log_create_error(handle, r);
@ -1019,7 +1095,23 @@ _public_ PAM_EXTERN int pam_sm_open_session(
"Not creating session: %s", bus_error_message(&error, r));
/* We are already in a session, don't do anything */
goto success;
} else {
} else if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
pam_debug_syslog(handle, debug,
"CreateSessionWithPIDFD() API is not available, retrying with CreateSession().");
m = sd_bus_message_unref(m);
r = create_session_message(bus,
handle,
&context,
true /* avoid_pidfd = */,
&m);
if (r < 0)
return pam_bus_log_create_error(handle, r);
sd_bus_error_free(&error);
r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
}
if (r < 0) {
pam_syslog(handle, LOG_ERR,
"Failed to create session: %s", bus_error_message(&error, r));
return PAM_SESSION_ERR;