From ea51a373d703cb8e830d45ff7336a6d06952f329 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Mon, 9 Nov 2026 09:06:11 -0500 Subject: [PATCH] fix(native): D-Bus logins work and Xwayland still admits root once peers read as root --- tools/nativeguest/hook/identity.c | 10 +++++++-- tools/nativeguest/hook/sockets.c | 37 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/tools/nativeguest/hook/identity.c b/tools/nativeguest/hook/identity.c index 83a5d5b40..0f759933e 100644 --- a/tools/nativeguest/hook/identity.c +++ b/tools/nativeguest/hook/identity.c @@ -715,11 +715,17 @@ int DDNATV_NSS_SYM(getgrnam_r)(const char *name, struct group *grp, char *buf, s // setgid() only pretend to), and sshd's unprivileged part checks after dropping them that it cannot // take them back, which ended it ("permanently_set_uid: was able to restore old [e]gid"). Like apt's // sandbox user (APT::Sandbox::User "root"), it then runs as the rest does. +// +// The app's own ids are root's as well: the rootfs lists root with them for proot, and a server +// that allows "localuser:root" compares getpwnam("root") with a peer's credentials, which the hook +// reports as 0. Xwayland refused gamescope's own connection when the two disagreed. static struct passwd *present_privsep_user_as_root(struct passwd *pwd) { if (pwd && pwd->pw_name && strcmp(pwd->pw_name, "sshd") == 0) { pwd->pw_uid = 0; pwd->pw_gid = 0; } + if (pwd && pwd->pw_uid == real_uid()) pwd->pw_uid = 0; + if (pwd && pwd->pw_gid == real_gid()) pwd->pw_gid = 0; return pwd; } @@ -728,11 +734,11 @@ struct passwd *DDNATV_NSS_SYM(getpwuid)(uid_t uid) { static struct passwd *(*orig_getpwuid)(uid_t) = NULL; if (!orig_getpwuid) orig_getpwuid = (struct passwd *(*)(uid_t))dlsym(RTLD_NEXT, "getpwuid"); struct passwd *res = orig_getpwuid ? orig_getpwuid(uid) : NULL; - if (res) return res; + if (res) return present_privsep_user_as_root(res); #endif int err = ddnatv_lookup_passwd(NULL, uid, 0, &s_pwd_storage, s_pwd_buf, sizeof(s_pwd_buf)); - if (err == 0) return &s_pwd_storage; + if (err == 0) return present_privsep_user_as_root(&s_pwd_storage); return NULL; } diff --git a/tools/nativeguest/hook/sockets.c b/tools/nativeguest/hook/sockets.c index 7188b51d6..368618873 100644 --- a/tools/nativeguest/hook/sockets.c +++ b/tools/nativeguest/hook/sockets.c @@ -221,6 +221,42 @@ ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) { return orig_sendmsg ? orig_sendmsg(sockfd, msg, flags) : -1; } +static void present_peer_as_root(struct ucred *cred) { + if (cred->uid == (uid_t)call_orig_syscall(DDNATV_NR_REAL_GETUID, 0, 0, 0, 0, 0, 0)) cred->uid = 0; + if (cred->gid == (gid_t)call_orig_syscall(DDNATV_NR_REAL_GETGID, 0, 0, 0, 0, 0, 0)) cred->gid = 0; +} + +// A server compares a peer's credentials with the user the peer claims, which is getuid()'s 0 +// here, while the kernel reports the app's ids: dbus-daemon refused every EXTERNAL login ("Did not +// receive a reply"), so the Plasma desktop never started. +int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { + static int (*orig_getsockopt)(int, int, int, void *, socklen_t *) = NULL; + if (!orig_getsockopt) orig_getsockopt = (int (*)(int, int, int, void *, socklen_t *))dlsym(RTLD_NEXT, "getsockopt"); + if (!orig_getsockopt) { + errno = ENOSYS; + return -1; + } + int ret = orig_getsockopt(sockfd, level, optname, optval, optlen); + if (ret == 0 && level == SOL_SOCKET && optname == SO_PEERCRED && optval && optlen && *optlen >= sizeof(struct ucred)) { + struct ucred cred; + memcpy(&cred, optval, sizeof(cred)); + present_peer_as_root(&cred); + memcpy(optval, &cred, sizeof(cred)); + } + return ret; +} + +static void received_credentials_as_root(struct msghdr *msg) { + if (!msg || !msg->msg_control || msg->msg_controllen < sizeof(struct cmsghdr)) return; + for (struct cmsghdr *c = CMSG_FIRSTHDR(msg); c; c = CMSG_NXTHDR(msg, c)) { + if (c->cmsg_level != SOL_SOCKET || c->cmsg_type != SCM_CREDENTIALS || c->cmsg_len < CMSG_LEN(sizeof(struct ucred))) continue; + struct ucred cred; + memcpy(&cred, CMSG_DATA(c), sizeof(cred)); + present_peer_as_root(&cred); + memcpy(CMSG_DATA(c), &cred, sizeof(cred)); + } +} + ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) { static ssize_t (*orig_recvfrom)(int, void *, size_t, int, struct sockaddr *, socklen_t *) = NULL; @@ -251,6 +287,7 @@ ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) { socklen_t cap = msg ? msg->msg_namelen : 0; ssize_t ret = orig_recvmsg ? orig_recvmsg(sockfd, msg, flags) : -1; + if (ret >= 0) received_credentials_as_root(msg); if (ret > 0 && msg && msg->msg_name) { int saved_errno = errno; restore_dns_source(sockfd, (struct sockaddr *)msg->msg_name, cap, &msg->msg_namelen);