check_symbol_exists(LOG_PERROR "syslog.h" HAVE_DECL_LOG_PERROR)
check_symbol_exists(MSG_NOSIGNAL "sys/socket.h" HAVE_DECL_MSG_NOSIGNAL)
check_symbol_exists(SCM_RIGHTS "sys/types.h;sys/socket.h;sys/un.h" HAVE_UNIX_FD_PASSING)
+check_symbol_exists(SYS_pidfd_open "sys/syscall.h" HAVE_DECL_SYS_PIDFD_OPEN) # dbus-sysdeps-unix.c
check_symbol_exists(accept4 "sys/socket.h" HAVE_ACCEPT4)
check_symbol_exists(clearenv "stdlib.h" HAVE_CLEARENV) # dbus-sysdeps.c
check_symbol_exists(close_range "unistd.h" HAVE_CLOSE_RANGE) # dbus-sysdeps-unix.c
#cmakedefine01 HAVE_DECL_ENVIRON
#cmakedefine01 HAVE_DECL_LOG_PERROR
#cmakedefine01 HAVE_DECL_MSG_NOSIGNAL
+#cmakedefine01 HAVE_DECL_SYS_PIDFD_OPEN
#endif // _DBUS_CONFIG_H
AC_CHECK_HEADERS(sys/vfs.h, [AC_CHECK_FUNCS(fstatfs)])
AC_CHECK_HEADERS([linux/magic.h])
+AC_CHECK_DECLS([SYS_pidfd_open], [], [], [[ #include <sys/syscall.h> ]])
+
#### Set up final flags
LIBDBUS_LIBS="$THREAD_LIBS $NETWORK_libs $SYSTEMD_LIBS"
AC_SUBST([LIBDBUS_LIBS])
auth->desired_identity))
goto out_3;
- /* Copy process ID from the socket credentials if it's there
+ /* Copy process ID (and PID FD) from the socket credentials if it's there
*/
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_PROCESS_FD,
+ auth->credentials))
+ goto out_3;
if (!_dbus_credentials_add_credential (auth->authorized_identity,
DBUS_CREDENTIAL_UNIX_PROCESS_ID,
auth->credentials))
/* also copy misc process info from the socket credentials
*/
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_PROCESS_FD,
+ auth->credentials))
+ return FALSE;
+
if (!_dbus_credentials_add_credential (auth->authorized_identity,
DBUS_CREDENTIAL_UNIX_PROCESS_ID,
auth->credentials))
/* We want to be anonymous (clear in case some other protocol got midway through I guess) */
_dbus_credentials_clear (auth->desired_identity);
- /* Copy process ID from the socket credentials
+ /* Copy process ID (and PID FD) from the socket credentials
*/
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_PROCESS_FD,
+ auth->credentials))
+ return FALSE;
+
if (!_dbus_credentials_add_credential (auth->authorized_identity,
DBUS_CREDENTIAL_UNIX_PROCESS_ID,
auth->credentials))
#include <config.h>
#include <stdlib.h>
#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_SYSCALL_H
+#include <sys/syscall.h>
+#endif
#include "dbus-credentials.h"
#include "dbus-internals.h"
+#ifdef DBUS_UNIX
+#include "dbus-sysdeps-unix.h"
+#endif
/**
* @defgroup DBusCredentials Credentials provable through authentication
dbus_gid_t *unix_gids;
size_t n_unix_gids;
dbus_pid_t pid;
+ int pid_fd;
char *windows_sid;
char *linux_security_label;
void *adt_audit_data;
creds->unix_gids = NULL;
creds->n_unix_gids = 0;
creds->pid = DBUS_PID_UNSET;
+ creds->pid_fd = -1;
creds->windows_sid = NULL;
creds->linux_security_label = NULL;
creds->adt_audit_data = NULL;
dbus_free (credentials->windows_sid);
dbus_free (credentials->linux_security_label);
dbus_free (credentials->adt_audit_data);
+#ifdef DBUS_UNIX
+ if (credentials->pid_fd >= 0)
+ {
+ close (credentials->pid_fd);
+ credentials->pid_fd = -1;
+ }
+#endif
dbus_free (credentials);
}
}
/**
- * Add a UNIX process ID to the credentials.
+ * Add a UNIX process ID to the credentials. If the
+ * process ID FD is set, it will always take
+ * precendence when querying the PID of this
+ * credential.
*
* @param credentials the object
* @param pid the process ID
return TRUE;
}
+/**
+ * Add a UNIX process ID FD to the credentials. The
+ * FD is now owned by the credentials object.
+ *
+ * @param credentials the object
+ * @param pid_fd the process ID FD
+ * @returns #FALSE if no memory
+ */
+#ifndef DBUS_UNIX
+_DBUS_GNUC_NORETURN
+#endif
+void
+_dbus_credentials_take_pid_fd (DBusCredentials *credentials,
+ int pid_fd)
+{
+#ifdef DBUS_UNIX
+ if (credentials->pid_fd >= 0)
+ close (credentials->pid_fd);
+ credentials->pid_fd = pid_fd;
+#else
+ _dbus_assert_not_reached ("pidfd never set on non-Unix");
+#endif
+}
+
/**
* Add a UNIX user ID to the credentials.
*
switch (type)
{
case DBUS_CREDENTIAL_UNIX_PROCESS_ID:
- return credentials->pid != DBUS_PID_UNSET;
+ return credentials->pid != DBUS_PID_UNSET ||
+ credentials->pid_fd >= 0;
+ case DBUS_CREDENTIAL_UNIX_PROCESS_FD:
+ return credentials->pid_fd >= 0;
case DBUS_CREDENTIAL_UNIX_USER_ID:
return credentials->unix_uid != DBUS_UID_UNSET;
case DBUS_CREDENTIAL_UNIX_GROUP_IDS:
/**
* Gets the UNIX process ID in the credentials, or #DBUS_PID_UNSET if
* the credentials object doesn't contain a process ID.
+ * If the PID FD is set, it will first try to resolve from it, and
+ * only return the stored PID if that fails.
*
* @param credentials the object
* @returns UNIX process ID
dbus_pid_t
_dbus_credentials_get_pid (DBusCredentials *credentials)
{
+#ifdef DBUS_UNIX
+ dbus_pid_t pid;
+
+ if (credentials->pid_fd >= 0)
+ {
+ pid = _dbus_resolve_pid_fd (credentials->pid_fd);
+ if (pid > 0)
+ return pid;
+ }
+#endif
+
return credentials->pid;
}
+/**
+ * Gets the UNIX process ID FD in the credentials as obtained by 'safe'
+ * means (e.g.: Linux's SO_PEERPIDFD), or -1 if the credentials object
+ * doesn't contain a process ID FD. The file FD is owned by the credentials
+ * object and must not be closed by the caller.
+ *
+ * @param credentials the object
+ * @returns UNIX process ID FD
+ */
+int
+_dbus_credentials_get_pid_fd (DBusCredentials *credentials)
+{
+ return credentials->pid_fd;
+}
+
/**
* Gets the UNIX user ID in the credentials, or #DBUS_UID_UNSET if
* the credentials object doesn't contain a user ID.
{
return
credentials->pid == DBUS_PID_UNSET &&
+ credentials->pid_fd == -1 &&
credentials->unix_uid == DBUS_UID_UNSET &&
credentials->unix_gids == NULL &&
credentials->n_unix_gids == 0 &&
DBusCredentials *other_credentials)
{
return
+ _dbus_credentials_add_credential (credentials,
+ DBUS_CREDENTIAL_UNIX_PROCESS_FD,
+ other_credentials) &&
_dbus_credentials_add_credential (credentials,
DBUS_CREDENTIAL_UNIX_PROCESS_ID,
other_credentials) &&
if (!_dbus_credentials_add_adt_audit_data (credentials, other_credentials->adt_audit_data, other_credentials->adt_audit_data_size))
return FALSE;
}
+ /* _dbus_dup() is only available on UNIX platforms. */
+#ifdef DBUS_UNIX
+ else if (which == DBUS_CREDENTIAL_UNIX_PROCESS_FD &&
+ other_credentials->pid_fd >= 0)
+ {
+ int pid_fd = _dbus_dup (other_credentials->pid_fd, NULL);
+
+ if (pid_fd < 0)
+ return FALSE;
+
+ _dbus_credentials_take_pid_fd (credentials, pid_fd);
+ }
+#endif
return TRUE;
}
_dbus_credentials_clear (DBusCredentials *credentials)
{
credentials->pid = DBUS_PID_UNSET;
+#ifdef DBUS_UNIX
+ if (credentials->pid_fd >= 0)
+ {
+ close (credentials->pid_fd);
+ credentials->pid_fd = -1;
+ }
+#endif
credentials->unix_uid = DBUS_UID_UNSET;
dbus_free (credentials->unix_gids);
credentials->unix_gids = NULL;
goto oom;
join = TRUE;
}
- if (credentials->pid != DBUS_PID_UNSET)
+ if (credentials->pid != DBUS_PID_UNSET || credentials->pid_fd >= 0)
{
- if (!_dbus_string_append_printf (string, "%spid=" DBUS_PID_FORMAT, join ? " " : "", credentials->pid))
+ if (!_dbus_string_append_printf (string,
+ "%spid=" DBUS_PID_FORMAT,
+ join ? " " : "",
+ _dbus_credentials_get_pid (credentials)))
goto oom;
join = TRUE;
}
join = TRUE;
}
+ if (credentials->pid_fd >= 0)
+ {
+ if (!_dbus_string_append_printf (string, "%spidfd=%d", join ? " " : "", credentials->pid_fd))
+ goto oom;
+ join = TRUE;
+ }
+
return TRUE;
oom:
return FALSE;
DBUS_CREDENTIAL_UNIX_GROUP_IDS,
DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID,
DBUS_CREDENTIAL_LINUX_SECURITY_LABEL,
- DBUS_CREDENTIAL_WINDOWS_SID
+ DBUS_CREDENTIAL_WINDOWS_SID,
+ DBUS_CREDENTIAL_UNIX_PROCESS_FD,
} DBusCredentialType;
DBUS_PRIVATE_EXPORT
dbus_bool_t _dbus_credentials_add_pid (DBusCredentials *credentials,
dbus_pid_t pid);
DBUS_PRIVATE_EXPORT
+void _dbus_credentials_take_pid_fd (DBusCredentials *credentials,
+ int pid_fd);
+DBUS_PRIVATE_EXPORT
dbus_bool_t _dbus_credentials_add_unix_uid (DBusCredentials *credentials,
dbus_uid_t uid);
DBUS_PRIVATE_EXPORT
DBUS_PRIVATE_EXPORT
dbus_pid_t _dbus_credentials_get_pid (DBusCredentials *credentials);
DBUS_PRIVATE_EXPORT
+int _dbus_credentials_get_pid_fd (DBusCredentials *credentials);
+DBUS_PRIVATE_EXPORT
dbus_uid_t _dbus_credentials_get_unix_uid (DBusCredentials *credentials);
DBUS_PRIVATE_EXPORT
dbus_bool_t _dbus_credentials_get_unix_gids (DBusCredentials *credentials,
dbus_gid_t primary_gid_read;
dbus_pid_t pid_read;
int bytes_read;
+ int pid_fd_read;
#ifdef HAVE_CMSGCRED
union {
uid_read = DBUS_UID_UNSET;
primary_gid_read = DBUS_GID_UNSET;
pid_read = DBUS_PID_UNSET;
+ pid_fd_read = -1;
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
primary_gid_read = cr.gid;
#endif
}
+
+#ifdef SO_PEERPIDFD
+ /* If we have SO_PEERCRED we might also have SO_PEERPIDFD, which
+ * allows to pin the process ID, and is available on Linux since v6.5. */
+ cr_len = sizeof (int);
+
+ if (getsockopt (client_fd.fd, SOL_SOCKET, SO_PEERPIDFD, &pid_fd_read, &cr_len) != 0)
+ {
+ _dbus_verbose ("Failed to getsockopt(SO_PEERPIDFD): %s\n",
+ _dbus_strerror (errno));
+ }
+ else if (cr_len != sizeof (int))
+ {
+ _dbus_verbose ("Failed to getsockopt(SO_PEERPIDFD), returned %d bytes, expected %d\n",
+ cr_len, (int) sizeof (int));
+ }
+#endif
+
#elif defined(HAVE_UNPCBID) && defined(LOCAL_PEEREID)
/* Another variant of the above - used on NetBSD
*/
pid_read,
uid_read);
+ /* Assign this first, so we don't have to close it manually in case one of
+ * the next steps fails. */
+ if (pid_fd_read >= 0)
+ _dbus_credentials_take_pid_fd (credentials, pid_fd_read);
+
if (pid_read != DBUS_PID_UNSET)
{
if (!_dbus_credentials_add_pid (credentials, pid_read))
dbus_bool_t
_dbus_credentials_add_from_current_process (DBusCredentials *credentials)
{
+ dbus_pid_t pid = _dbus_getpid ();
+
/* The POSIX spec certainly doesn't promise this, but
* we need these assertions to fail as soon as we're wrong about
* it so we can do the porting fixups
_DBUS_STATIC_ASSERT (sizeof (uid_t) <= sizeof (dbus_uid_t));
_DBUS_STATIC_ASSERT (sizeof (gid_t) <= sizeof (dbus_gid_t));
- if (!_dbus_credentials_add_pid(credentials, _dbus_getpid()))
+#if HAVE_DECL_SYS_PIDFD_OPEN
+ /* Normally this syscall would have a race condition, but we can trust
+ * that our own process isn't going to exit, so the pid won't get reused. */
+ int pid_fd = (int) syscall (SYS_pidfd_open, pid, 0);
+ if (pid_fd >= 0)
+ _dbus_credentials_take_pid_fd (credentials, pid_fd);
+#endif
+ if (!_dbus_credentials_add_pid (credentials, pid))
return FALSE;
if (!_dbus_credentials_add_unix_uid(credentials, _dbus_geteuid()))
return FALSE;
return TRUE;
}
+/**
+ * Resolve the PID from the PID FD, if any. This allows us to avoid
+ * PID reuse attacks. Returns DBUS_PID_UNSET if the PID could not be resolved.
+ * Note that this requires being able to read /proc/self/fdinfo/<FD>,
+ * which is created as 600 and owned by the original UID that the
+ * process started as. So it cannot work when the start as root and
+ * drop privileges mechanism is in use (the systemd unit no longer
+ * does this, but third-party init-scripts might).
+ *
+ * @param pid_fd the PID FD
+ * @returns the resolved PID if found, DBUS_PID_UNSET otherwise
+ */
+dbus_pid_t
+_dbus_resolve_pid_fd (int pid_fd)
+{
+#ifdef __linux__
+ DBusError error = DBUS_ERROR_INIT;
+ DBusString content = _DBUS_STRING_INIT_INVALID;
+ DBusString filename = _DBUS_STRING_INIT_INVALID;
+ dbus_pid_t result = DBUS_PID_UNSET;
+ int pid_index;
+
+ if (pid_fd < 0)
+ goto out;
+
+ if (!_dbus_string_init (&content))
+ goto out;
+
+ if (!_dbus_string_init (&filename))
+ goto out;
+
+ if (!_dbus_string_append_printf (&filename, "/proc/self/fdinfo/%d", pid_fd))
+ goto out;
+
+ if (!_dbus_file_get_contents (&content, &filename, &error))
+ {
+ _dbus_verbose ("Cannot read '/proc/self/fdinfo/%d', unable to resolve PID, %s: %s\n",
+ pid_fd, error.name, error.message);
+ goto out;
+ }
+
+ /* Ensure we are not reading PPid, either it's the first line of the file or
+ * there's a newline before it. */
+ if (!_dbus_string_find (&content, 0, "Pid:", &pid_index) ||
+ (pid_index > 0 && _dbus_string_get_byte (&content, pid_index - 1) != '\n'))
+ {
+ _dbus_verbose ("Cannot find 'Pid:' in '/proc/self/fdinfo/%d', unable to resolve PID\n",
+ pid_fd);
+ goto out;
+ }
+
+ if (!_dbus_string_parse_uint (&content, pid_index + strlen ("Pid:"), &result, NULL))
+ {
+ _dbus_verbose ("Cannot parse 'Pid:' from '/proc/self/fdinfo/%d', unable to resolve PID\n",
+ pid_fd);
+ goto out;
+ }
+
+out:
+ _dbus_string_free (&content);
+ _dbus_string_free (&filename);
+ dbus_error_free (&error);
+
+ if (result <= 0)
+ return DBUS_PID_UNSET;
+
+ return result;
+#else
+ return DBUS_PID_UNSET;
+#endif
+
+}
+
/**
* Append to the string the identity we would like to have when we
* authenticate, on UNIX this is the current process UID and on
dbus_bool_t _dbus_socket_can_pass_unix_fd(DBusSocket fd);
+/* PID FDs are Linux-specific. */
+#ifdef DBUS_WIN
+static inline dbus_pid_t _dbus_resolve_pid_fd (int pid_fd)
+{
+ return DBUS_PID_UNSET;
+}
+
+#else
+DBUS_PRIVATE_EXPORT
+dbus_pid_t _dbus_resolve_pid_fd (int pid_fd);
+#endif
+
/** Opaque type representing an atomically-modifiable integer
* that can be used from multiple threads.
*/
)
)
+config.set10('HAVE_DECL_SYS_PIDFD_OPEN',
+ cc.has_header_symbol(
+ 'sys/syscall.h',
+ 'SYS_pidfd_open',
+ args: compile_args_c,
+ )
+)
+
###############################################################################
# Project options
DBusCredentials *creds;
DBusCredentials *creds2;
DBusString str;
+ DBusString str2;
const dbus_gid_t *gids;
size_t n;
+ dbus_pid_t pid = _dbus_getpid();
if (test_data_dir == NULL)
return TRUE;
- creds = make_credentials (12, 511, 1, SAMPLE_SID);
+ creds = make_credentials (12, pid, 1, SAMPLE_SID);
if (creds == NULL)
_dbus_test_fatal ("oom");
_dbus_assert (_dbus_credentials_include (creds, DBUS_CREDENTIAL_WINDOWS_SID));
_dbus_assert (_dbus_credentials_get_unix_uid (creds) == 12);
- _dbus_assert (_dbus_credentials_get_pid (creds) == 511);
+ _dbus_assert (_dbus_credentials_get_pid (creds) == pid);
_dbus_assert (strcmp (_dbus_credentials_get_windows_sid (creds), SAMPLE_SID) == 0);
_dbus_assert (_dbus_credentials_get_unix_gids (creds, &gids, &n));
_dbus_assert (n == 4);
_dbus_assert (_dbus_credentials_include (creds2, DBUS_CREDENTIAL_WINDOWS_SID));
_dbus_assert (_dbus_credentials_get_unix_uid (creds2) == 12);
- _dbus_assert (_dbus_credentials_get_pid (creds2) == 511);
+ _dbus_assert (_dbus_credentials_get_pid (creds2) == pid);
_dbus_assert (strcmp (_dbus_credentials_get_windows_sid (creds2), SAMPLE_SID) == 0);
_dbus_assert (_dbus_credentials_get_unix_gids (creds2, &gids, &n));
_dbus_assert (n == 4);
_dbus_credentials_unref (creds2);
/* Same user, but not a superset, if groups are different */
- creds2 = make_credentials (12, 511, 2, SAMPLE_SID);
+ creds2 = make_credentials (12, pid, 2, SAMPLE_SID);
if (creds2 == NULL)
_dbus_test_fatal ("oom");
_dbus_credentials_unref (creds2);
/* Groups being in the same order make no difference */
- creds2 = make_credentials (12, 511, 3, SAMPLE_SID);
+ creds2 = make_credentials (12, pid, 3, SAMPLE_SID);
if (creds2 == NULL)
_dbus_test_fatal ("oom");
_dbus_credentials_unref (creds);
/* Make some more realistic credentials blobs to test stringification */
- if (!_dbus_string_init (&str))
+ if (!_dbus_string_init (&str) || !_dbus_string_init (&str2))
_dbus_test_fatal ("oom");
creds = make_credentials (12, DBUS_PID_UNSET, 0, NULL);
_dbus_credentials_unref (creds);
- creds = make_credentials (12, 511, 1, NULL);
+ creds = make_credentials (12, pid, 1, NULL);
if (creds == NULL)
_dbus_test_fatal ("oom");
if (!_dbus_credentials_to_string_append (creds, &str))
_dbus_test_fatal ("oom");
+ if (!_dbus_string_append_printf(&str2, "uid=12 pid=" DBUS_PID_FORMAT " gid=42 gid=123 gid=1000 gid=5678", pid))
+ _dbus_test_fatal ("oom");
+
_dbus_test_diag ("Unix complete set: %s", _dbus_string_get_const_data (&str));
- _dbus_assert (strcmp (_dbus_string_get_const_data (&str),
- "uid=12 pid=511 gid=42 gid=123 gid=1000 gid=5678") == 0);
+ _dbus_assert (strcmp (_dbus_string_get_const_data (&str), _dbus_string_get_const_data (&str2)) == 0);
_dbus_credentials_unref (creds);
_dbus_credentials_unref (creds);
- creds = make_credentials (DBUS_UID_UNSET, 511, 0, SAMPLE_SID);
+ creds = make_credentials (DBUS_UID_UNSET, pid, 0, SAMPLE_SID);
if (creds == NULL)
_dbus_test_fatal ("oom");
- if (!_dbus_string_set_length (&str, 0))
+ if (!_dbus_string_set_length (&str, 0) || !_dbus_string_set_length (&str2, 0))
_dbus_test_fatal ("oom");
if (!_dbus_credentials_to_string_append (creds, &str))
_dbus_test_fatal ("oom");
+ if (!_dbus_string_append_printf(&str2, "pid=" DBUS_PID_FORMAT " sid=" SAMPLE_SID, pid))
+ _dbus_test_fatal ("oom");
+
_dbus_test_diag ("Windows complete set: %s", _dbus_string_get_const_data (&str));
- _dbus_assert (strcmp (_dbus_string_get_const_data (&str),
- "pid=511 sid=" SAMPLE_SID) == 0);
+ _dbus_assert (strcmp (_dbus_string_get_const_data (&str), _dbus_string_get_const_data (&str2)) == 0);
_dbus_credentials_unref (creds);
_dbus_string_free (&str);
+ _dbus_string_free (&str2);
return TRUE;
}