// Copyright 2016 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // This file implements a wrapper to run the virtual me2me session within a // proper PAM session. It will generally be run as root and drop privileges to // the specified user before running the me2me session script. #ifdef UNSAFE_BUFFERS_BUILD // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. #pragma allow_unsafe_buffers #endif // Usage: user-session start [--foreground] [--user user] [-- SCRIPT_ARGS...] // // Options: // --foreground - Don't daemonize. // --user - Create a session for the specified user. Required when // running as root, not allowed when running as a normal user. // SCRIPT_ARGS - Arguments following -- are passed to the script verbatim. #include <fcntl.h> #include <grp.h> #include <limits.h> #include <pwd.h> #include <security/pam_appl.h> #include <signal.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <cerrno> #include <cstdio> #include <cstdlib> #include <cstring> #include <ctime> #include <map> #include <memory> #include <optional> #include <string> #include <string_view> #include <tuple> #include <utility> #include <vector> #include "base/environment.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/process/launch.h" #include "base/strings/strcat.h" #include "base/strings/string_util.h" namespace { // This is the file descriptor used for the Python session script to pass us // messages during startup. It must be kept in sync with USER_SESSION_MESSAGE_FD // in linux_me2me_host.py. It should be high enough that login scripts are // unlikely to interfere with it, but is otherwise arbitrary. const int kMessageFd = …; // This is the exit code the Python session script will use to signal that the // user-session wrapper should restart instead of exiting. It must be kept in // sync with RELAUNCH_EXIT_CODE in linux_me2me_host.py const int kRelaunchExitCode = …; const char kPamName[] = …; const char kScriptName[] = …; const char kStartCommand[] = …; const char kForegroundFlag[] = …; const char kUserFlag[] = …; const char kExeSymlink[] = …; // This template will be formatted by strftime and then used by mkstemp const char kLogFileTemplate[] = …; // The filename for the latest log symlink. constexpr char kLatestLogSymlink[] = …; const char kUsageMessage[] = …; // A list of variable to pass through to the child environment. Should be kept // in sync with remoting_user_session_wrapper.sh for testing. const char* const kPassthroughVariables[] = …; // Holds the null-terminated path to this executable. This is obtained at // startup, since it may be harder to obtain later. (E.g., Linux will append // " (deleted)" if the file has been replaced by an update.) char gExecutablePath[PATH_MAX] = …; void PrintUsage() { … } // Shell-escapes a single argument in a way that is compatible with various // different shells. Returns nullopt when argument contains a newline, which // can't be represented in a cross-shell fashion. std::optional<std::string> ShellEscapeArgument( const std::string_view argument) { … } // PAM conversation function. Since the wrapper runs in a non-interactive // context, log any messages, but return an error if asked to provide user // input. extern "C" int Converse(int num_messages, const struct pam_message** messages, struct pam_response** responses, void* context) { … } const struct pam_conv kPamConversation = …; // Wrapper class for working with PAM and cleaning up in an RAII fashion class PamHandle { … }; // Initializes the gExecutablePath global to the location of the running // executable. Should be called at program start. void DetermineExecutablePath() { … } // Returns the expected location of the session script based on the path to // this executable. std::string FindScriptPath() { … } // Execs the me2me script. // This function is called after forking and dropping privileges. It never // returns. [[noreturn]] void ExecMe2MeScript(base::EnvironmentMap environment, const struct passwd* pwinfo, const std::vector<std::string>& script_args) { … } // Either |user| must be set when running as root, xor the real user ID must be // properly set when running as a user. void Relaunch(const std::optional<std::string>& user, const std::vector<std::string>& script_args) { … } // Runs the me2me script in a PAM session. Exits the program on failure. // If chown_log is true, the owner and group of the file associated with stdout // will be changed to the target user. If match_uid is specified, this function // will fail if the final user id does not match the one provided. If // script_args is not empty, the contained arguments will be passed on to the // me2me script. // // Returns: whether the session should be relaunched. bool ExecuteSession(std::string user, bool chown_log, std::optional<uid_t> match_uid, const std::vector<std::string>& script_args) { … } struct LogFile { … }; // Opens a temp file for logging. Exits the program on failure. // Returns open file descriptor and path to log file. LogFile OpenLogFile() { … } // Find the username for the current user. If either USER or LOGNAME is set to // a user matching our real user id, we return that. Otherwise, we use getpwuid // to attempt a reverse lookup. Note: It's possible for multiple usernames to // share the same user id (e.g., to allow a user to have logins with different // home directories or group membership, but be considered the same user as far // as file permissions are concerned). Consulting USER/LOGNAME allows us to pick // the correct entry in these circumstances. std::string FindCurrentUsername() { … } // Handle SIGINT and SIGTERM by printing a message and reraising the signal. // This handler expects to be registered with the SA_RESETHAND and SA_NODEFER // options to sigaction. (Don't register using signal.) void HandleInterrupt(int signal) { … } // Handle SIGALRM timeout void HandleAlarm(int) { … } // Relay messages from the host session and then exit. void WaitForMessagesAndExit(int read_fd, const std::string& log_name) { … } // Daemonizes the process. Output is redirected to a log file. Exits the program // on failure. Only returns in the child process. // // When executed by root (almost certainly via the init script), or if a pipe // cannot be created, the parent will immediately exit. When executed by a // user, the parent process will drop privileges and wait for the host to // start, relaying any start-up messages to stdout. // // TODO(lambroslambrou): Having stdout/stderr redirected to a log file is not // ideal - it could create a filesystem DoS if the daemon or a child process // were to write excessive amounts to stdout/stderr. Ideally, stdout/stderr // should be redirected to a pipe or socket, and a process at the other end // should consume the data and write it to a logging facility which can do // data-capping or log-rotation. The 'logger' command-line utility could be // used for this, but it might cause too much syslog spam. void Daemonize() { … } } // namespace int main(int argc, char** argv) { … }