Temporarily revert 9980fa0a10

Turns out that it causes regressions for some users (see #1443). I'm going to move this change into a branch for testing and debugging and merge it back in once it's working reliably for everyone.

This change isn't too important for most users of Darling; at the moment, the only significant thing it does is allow xcodebuild to work, but that's not essential at the moment.
This commit is contained in:
Ariel Abreu 2023-11-07 13:41:31 -05:00
parent 141c24aaa8
commit 34351655a4
6 changed files with 11 additions and 505 deletions

View File

@ -3,9 +3,7 @@ project(shellspawn)
add_definitions(-nostdinc)
add_darling_executable(shellspawn shellspawn.c duct_signals.c)
add_darling_executable(shellsession shellsession.c)
install(TARGETS shellspawn shellsession DESTINATION libexec/darling/usr/libexec)
install(FILES org.darlinghq.shellsession.plist DESTINATION libexec/darling/System/Library/LaunchDaemons)
install(FILES org.darlinghq.shellspawn.plist DESTINATION libexec/darling/System/Library/LaunchAgents)
install(TARGETS shellspawn DESTINATION libexec/darling/usr/libexec)
install(FILES org.darlinghq.shellspawn.plist DESTINATION libexec/darling/System/Library/LaunchDaemons)

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.darlinghq.shellsession</string>
<key>ProgramArguments</key>
<array>
<string>/usr/libexec/shellsession</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View File

@ -1,373 +0,0 @@
/*
* This file is part of Darling.
*
* Copyright (C) 2023 Darling Developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/syslimits.h>
#include <errno.h>
#include <pwd.h>
#include <liblaunch/launch_priv.h>
#include <bootstrap_priv.h>
#include <vproc_priv.h>
#include "shellsession.h"
#include "shellspawn.h"
#define MAX_ACCESS_TRIES 10
static void setup_socket_addr(struct sockaddr_un* addr);
static void setup_session_socket_addr(unsigned int uid, struct sockaddr_un* addr);
static int setup_socket(const struct sockaddr_un* addr);
static void listen_for_connections(int server_socket);
static void spawn_session(int sockfd);
static void send_client_socket(int sockfd, int socket_to_send);
static int try_connect_session(const struct sockaddr_un* addr);
static void run_session_manager(unsigned int uid, unsigned int gid);
int main(int argc, const char* const* argv) {
struct sockaddr_un server_addr;
int server_socket = -1;
setup_socket_addr(&server_addr);
server_socket = setup_socket(&server_addr);
if (server_socket < 0) {
exit(EXIT_FAILURE);
}
listen_for_connections(server_socket);
if (server_socket >= 0) {
close(server_socket);
}
return 0;
};
static void setup_socket_addr(struct sockaddr_un* addr) {
memset(addr, 0, sizeof(*addr));
addr->sun_family = AF_UNIX;
snprintf(addr->sun_path, sizeof(addr->sun_path), "%s", SHELLSESSION_SOCKPATH);
};
static void setup_session_socket_addr(unsigned int uid, struct sockaddr_un* addr) {
memset(addr, 0, sizeof(*addr));
addr->sun_family = AF_UNIX;
snprintf(addr->sun_path, sizeof(addr->sun_path), "%s.%d", SHELLSPAWN_SOCKPATH, uid);
};
static int setup_socket(const struct sockaddr_un* addr) {
int server_socket = -1;
server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("Creating unix socket");
goto err;
}
fcntl(server_socket, F_SETFD, FD_CLOEXEC);
unlink(addr->sun_path);
if (bind(server_socket, (struct sockaddr*)addr, sizeof(*addr)) == -1) {
perror("Binding the unix socket");
goto err;
}
chmod(addr->sun_path, S_IRUSR | S_IWUSR);
if (listen(server_socket, 1) == -1) {
perror("Listening on unix socket");
goto err;
}
return server_socket;
err:
if (server_socket >= 0) {
close(server_socket);
}
return -1;
};
static void listen_for_connections(int server_socket) {
int sock;
struct sockaddr_un client_addr;
socklen_t len = sizeof(client_addr);
while (true) {
sock = accept(server_socket, (struct sockaddr*)&client_addr, &len);
if (sock == -1) {
break;
}
if (fork() == 0) {
// we don't need the server socket
close(server_socket);
server_socket = -1;
fcntl(sock, F_SETFD, FD_CLOEXEC);
spawn_session(sock);
exit(EXIT_SUCCESS);
} else {
close(sock);
}
}
};
void spawn_session(int sockfd) {
shellsession_cmd_t cmd;
struct sockaddr_un session_addr;
int client_socket = -1;
char session_started_path[PATH_MAX];
int started_fd = -1;
if (read(sockfd, &cmd, sizeof(cmd)) != sizeof(cmd)) {
goto err;
}
setup_session_socket_addr(cmd.uid, &session_addr);
snprintf(session_started_path, sizeof(session_started_path), "%s.%d", SHELLSESSION_STARTED_PATH, cmd.uid);
started_fd = open(session_started_path, O_RDONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (started_fd < 0 && errno == EEXIST) {
// we already had a session set up; avoid setting up a new one.
// wait until shellspawn starts
for (int i = 0; i < MAX_ACCESS_TRIES; i++) {
if (access(session_addr.sun_path, F_OK) == 0) {
break;
}
sleep(1);
}
client_socket = try_connect_session(&session_addr);
if (client_socket < 0) {
goto err;
}
// now reply to the client with the session client socket
send_client_socket(sockfd, client_socket);
close(sockfd);
return;
} else if (started_fd < 0) {
perror("Failed to create session indicator file");
goto err;
}
// we didn't have a session set up already, so let's create one.
// we only need to create the session-running file; we don't need to write anything to it
close(started_fd);
if (fork() == 0) {
// this is the new session manager process
close(sockfd);
run_session_manager(cmd.uid, cmd.gid);
} else {
// wait until shellspawn starts
for (int i = 0; i < MAX_ACCESS_TRIES; i++) {
if (access(session_addr.sun_path, F_OK) == 0) {
break;
}
sleep(1);
}
client_socket = try_connect_session(&session_addr);
if (client_socket < 0) {
fprintf(stderr, "Error connecting to shellspawn session\n");
exit(EXIT_FAILURE);
}
// now reply to the client with the session client socket
send_client_socket(sockfd, client_socket);
close(sockfd);
}
return;
err:
close(sockfd);
};
static void send_client_socket(int sockfd, int socket_to_send) {
char dummy = '\0';
struct msghdr msg;
char cmsgbuf[CMSG_SPACE(sizeof(int))];
struct cmsghdr *cmptr;
struct iovec iov;
memset(&msg, 0, sizeof(msg));
memset(&iov, 0, sizeof(iov));
iov.iov_base = &dummy;
iov.iov_len = sizeof(dummy);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
cmptr = CMSG_FIRSTHDR(&msg);
cmptr->cmsg_len = CMSG_LEN(sizeof(int));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cmptr), &socket_to_send, sizeof(socket_to_send));
if (sendmsg(sockfd, &msg, 0) != sizeof(dummy)) {
fprintf(stderr, "Error sending reply: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
};
static int try_connect_session(const struct sockaddr_un* addr) {
int client_socket = -1;
client_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (client_socket < 0) {
goto err;
}
if (connect(client_socket, (struct sockaddr*)addr, sizeof(*addr)) != 0) {
goto err;
}
return client_socket;
err:
if (client_socket >= 0) {
close(client_socket);
}
return -1;
};
static void run_session_manager(unsigned int uid, unsigned int gid) {
const char* login = NULL;
struct passwd* pw = NULL;
char* homedirTmp = NULL;
mach_port_t subset = MACH_PORT_NULL;
mach_port_t dummyService = MACH_PORT_NULL;
kern_return_t kr = KERN_SUCCESS;
// we are the shellspawn instance for the user's session. this means we're in charge
// of the user's session and are responsible for setting it up as macOS would.
// we are essentially going to be doing the same kind of setup that LoginWindow
// would do on macOS.
// switch ourselves to run under the user's UID and GID (but under Darling, this is all fake anyways)
setgid(gid);
setuid(uid);
// fix up env vars to what they should be
pw = getpwuid(uid);
if (pw != NULL)
login = pw->pw_name;
if (!login)
login = getlogin();
setenv("PATH", "/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin", 1);
setenv("TMPDIR", "/private/tmp", 1);
asprintf(&homedirTmp, "HOME=/Users/%s", login);
putenv(homedirTmp);
homedirTmp = NULL; // `putenv()` assumes ownership of the string
//
// IMPORTANT NOTE
//
// okay, so i have no clue how this whole session switching is supposed to work.
// or rather, i do have *some* idea: we need to call `create_and_switch_to_per_session_launchd`
// to switch to a per-user launchd (which will also trigger LaunchAgents for the user).
// the problem is that the way this function works internally is that it calls `_vprocmgr_move_subset_to_user`.
// this function, in turn, moves the current subset into the new per-user launchd session.
// obviously, this means we must already have a subset prior to calling `create_and_switch_to_per_session_launchd`.
//
// the thing is, i can't see how (the modern version of) LoginWindow does this. maybe the API has changed since then
// and it no longer moves a subset into the new session, but i can't see it creating a new subset at any point during
// the session setup. but clearly, *we* need to do this, so let's go ahead and do so.
//
if ((kr = bootstrap_subset(bootstrap_port, mach_task_self(), &subset)) != KERN_SUCCESS) {
fprintf(stderr, "Failed to create bootstrap subset: %d\n", kr);
exit(EXIT_FAILURE);
}
// replace the bootstrap port with our subset port
if ((kr = task_set_bootstrap_port(mach_task_self(), subset)) != KERN_SUCCESS) {
fprintf(stderr, "Failed to replace bootstrap port: %d\n", kr);
exit(EXIT_FAILURE);
}
// we no longer need the old bootstrap port
mach_port_deallocate(mach_task_self(), bootstrap_port);
bootstrap_port = subset;
//
// ANOTHER IMPORTANT NOTE
//
// launchd requires the new subset to have at least one Mach service registered before switching to the per-user
// session. to that end, we simply create a bogus service here.
//
if ((kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &dummyService)) != KERN_SUCCESS) {
fprintf(stderr, "Failed to allocate dummy service port: %d\n", kr);
exit(EXIT_FAILURE);
}
if ((kr = mach_port_insert_right(mach_task_self(), dummyService, dummyService, MACH_MSG_TYPE_MAKE_SEND)) != KERN_SUCCESS) {
fprintf(stderr, "Failed to insert send right into dummy service port: %d\n", kr);
exit(EXIT_FAILURE);
}
if ((kr = bootstrap_register(bootstrap_port, "org.darlinghq.shellsession.dummy-service", dummyService)) != KERN_SUCCESS) {
fprintf(stderr, "Failed to register dummy service: %d\n", kr);
exit(EXIT_FAILURE);
}
// set up a launchd session
if (create_and_switch_to_per_session_launchd("shellspawn", LAUNCH_GLOBAL_ON_DEMAND) < 0) {
fprintf(stderr, "Failed to set up launchd user session.\n");
exit(EXIT_FAILURE);
}
// unset the global-on-demand flag to kickstart jobs that need to run at load
if (!_vproc_set_global_on_demand(false)) {
fprintf(stderr, "Failed to unset global-on-demand flag.\n");
exit(EXIT_FAILURE);
}
// now we just need to stay alive for launchd to keep our session alive
// (launchd will start shellspawn as a LaunchAgent, and that's what the `darling` command will actually send commands to)
while (true) {
pause();
}
};

View File

@ -1,37 +0,0 @@
/*
* This file is part of Darling.
*
* Copyright (C) 2023 Darling Developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _SHELLSESSION_H_
#define _SHELLSESSION_H_
#ifdef TESTING
#define SHELLSESSION_SOCKPATH "/tmp/shellsession.sock"
#define SHELLSESSION_STARTED_PATH "/tmp/shellsession.running"
#else
#define SHELLSESSION_SOCKPATH "/var/run/shellsession.sock"
#define SHELLSESSION_STARTED_PATH "/var/run/shellsession.running"
#endif
typedef struct shellsession_cmd {
unsigned int uid;
unsigned int gid;
} shellsession_cmd_t;
#endif // _SHELLSESSION_H_

View File

@ -64,10 +64,9 @@ void setupSocket(void)
{
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = SHELLSPAWN_SOCKPATH
};
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s.%d", SHELLSPAWN_SOCKPATH, getuid());
g_serverSocket = socket(AF_UNIX, SOCK_STREAM, 0);
if (g_serverSocket == -1)
{
@ -76,7 +75,7 @@ void setupSocket(void)
}
fcntl(g_serverSocket, F_SETFD, FD_CLOEXEC);
unlink(addr.sun_path);
unlink(SHELLSPAWN_SOCKPATH);
if (bind(g_serverSocket, (struct sockaddr*) &addr, sizeof(addr)) == -1)
{

View File

@ -37,7 +37,6 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include <pty.h>
#include <pwd.h>
#include "../shellspawn/shellspawn.h"
#include "../shellspawn/shellsession.h"
#include "darling.h"
#include "darling-config.h"
@ -170,7 +169,7 @@ int main(int argc, char ** argv)
{
char socketPath[4096];
snprintf(socketPath, sizeof(socketPath), "%s" SHELLSESSION_SOCKPATH, prefix);
snprintf(socketPath, sizeof(socketPath), "%s" SHELLSPAWN_SOCKPATH, prefix);
unlink(socketPath);
@ -546,20 +545,20 @@ static size_t escapeQuotes(char *dest, const char *src)
return len;
}
int connectToShellsession(void)
int connectToShellspawn(void)
{
struct sockaddr_un addr;
int sockfd;
// Connect to the shellsession daemon in the container
// Connect to the shellspawn daemon in the container
addr.sun_family = AF_UNIX;
#if USE_LINUX_4_11_HACK
addr.sun_path[0] = '\0';
strcpy(addr.sun_path, prefix);
strcat(addr.sun_path, SHELLSESSION_SOCKPATH);
strcat(addr.sun_path, SHELLSPAWN_SOCKPATH);
#else
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s" SHELLSESSION_SOCKPATH, prefix);
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s" SHELLSPAWN_SOCKPATH, prefix);
#endif
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
@ -578,66 +577,6 @@ int connectToShellsession(void)
return sockfd;
}
static int startShellspawnSession(int sockfd)
{
shellsession_cmd_t cmd;
cmd.uid = g_originalGid;
cmd.gid = g_originalGid;
if (write(sockfd, &cmd, sizeof(cmd)) != sizeof(cmd))
{
fprintf(stderr, "Error sending command to shellsession: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
char dummy = '\0';
char cmsgbuf[CMSG_SPACE(sizeof(int))];
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmptr;
int sessionFD;
memset(&msg, 0, sizeof(msg));
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
iov.iov_base = &dummy;
iov.iov_len = sizeof(dummy);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if (recvmsg(sockfd, &msg, 0) != sizeof(dummy))
{
fprintf(stderr, "Error receiving reply from shellsession: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
cmptr = CMSG_FIRSTHDR(&msg);
if (cmptr == NULL || cmptr->cmsg_level != SOL_SOCKET || cmptr->cmsg_type != SCM_RIGHTS)
{
fprintf(stderr, "Invalid reply from shellsession: no attached FD\n");
exit(EXIT_FAILURE);
}
if (cmptr->cmsg_len != CMSG_LEN(sizeof(int)))
{
fprintf(stderr, "Invalid reply from shellsession: invalid CMSG length %lu (expected %lu)\n", cmptr->cmsg_len, sizeof(int));
exit(EXIT_FAILURE);
}
memcpy(&sessionFD, CMSG_DATA(cmptr), sizeof(int));
if (sessionFD < 0)
{
fprintf(stderr, "Invalid reply from shellsession: invalid FD %d\n", sessionFD);
exit(EXIT_FAILURE);
}
return sessionFD;
}
void setupShellspawnEnv(int sockfd)
{
static const char* skip_vars[] = {
@ -763,8 +702,7 @@ void spawnShell(const char** argv)
else
buffer = NULL;
sockfd = connectToShellsession();
sockfd = startShellspawnSession(sockfd);
sockfd = connectToShellspawn();
setupShellspawnEnv(sockfd);
@ -788,9 +726,7 @@ void spawnBinary(const char* binary, const char** argv)
int fds[3], master;
int sockfd;
sockfd = connectToShellsession();
sockfd = startShellspawnSession(sockfd);
sockfd = connectToShellspawn();
setupShellspawnEnv(sockfd);
pushShellspawnCommand(sockfd, SHELLSPAWN_SETEXEC, binary);