Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(440)

Unified Diff: remoting/host/linux/remoting_user_session.cc

Issue 2939263003: Add support for CRD user-session to operate setuid (Closed)
Patch Set: Address review feedback Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « remoting/host/installer/linux/debian/chrome-remote-desktop.init ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: remoting/host/linux/remoting_user_session.cc
diff --git a/remoting/host/linux/remoting_user_session.cc b/remoting/host/linux/remoting_user_session.cc
index 2457033c61c3016dcbc52d76748687bb4cdc60fc..fb1f49cf7dc5d57d2b5234ec8924bc140fbb4218 100644
--- a/remoting/host/linux/remoting_user_session.cc
+++ b/remoting/host/linux/remoting_user_session.cc
@@ -6,11 +6,19 @@
// proper PAM session. It will generally be run as root and drop privileges to
// the specified user before running the me2me session script.
+// Usage: user-session start [--foreground] [user]
+//
+// 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.
+
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <grp.h>
+#include <limits.h>
#include <pwd.h>
#include <unistd.h>
@@ -29,9 +37,9 @@
#include <security/pam_appl.h>
-#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_path.h"
+#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/optional.h"
@@ -41,30 +49,20 @@
namespace {
const char kPamName[] = "chrome-remote-desktop";
-
-const char kHelpSwitchName[] = "help";
-const char kQuestionSwitchName[] = "?";
-const char kUserSwitchName[] = "user";
-const char kScriptSwitchName[] = "me2me-script";
-const char kForegroundSwitchName[] = "foreground";
+const char kScriptName[] = "chrome-remote-desktop";
+const char kForegroundFlag[] = "--foreground";
// This template will be formatted by strftime and then used by mkstemp
const char kLogFileTemplate[] =
"/tmp/chrome_remote_desktop_%Y%m%d_%H%M%S_XXXXXX";
const char kUsageMessage[] =
- "Usage: %s [options]\n"
- "\n"
- "Options:\n"
- " --help, -? - Print this message.\n"
- " --user=<user> - Create session as the specified user. "
- "(Must run as root.)\n"
- " --me2me-script=<script> - Location of the me2me python script "
- "(required)\n"
- " --foreground - Don't daemonize.\n";
-
-void PrintUsage(const base::FilePath& program_name) {
- std::printf(kUsageMessage, program_name.MaybeAsASCII().c_str());
+ "This program is not intended to be run by end users. To configure Chrome\n"
+ "Remote Desktop, please install the app from the Chrome Web Store:\n"
+ "https://chrome.google.com/remotedesktop\n";
+
+void PrintUsage() {
+ std::fputs(kUsageMessage, stderr);
}
// Shell-escapes a single argument in a way that is compatible with various
@@ -243,15 +241,66 @@ class PamHandle {
DISALLOW_COPY_AND_ASSIGN(PamHandle);
};
+std::string FindScriptPath() {
+ base::FilePath path;
+ bool result = base::ReadSymbolicLink(base::FilePath("/proc/self/exe"), &path);
+ PCHECK(result) << "Failed to determine binary location";
rkjnsn 2017/07/21 01:55:35 Using PCHECK here assumes the failure stems from t
Jamie 2017/07/24 20:43:10 I think this is fine.
+ CHECK(path.IsAbsolute()) << "Retrieved binary location not absolute";
+
+ return path.DirName().Append(kScriptName).value();
+}
+
+// Execs the me2me script.
+// This function is called after forking and dropping privileges. It never
+// returns.
+void ExecMe2MeScript(base::EnvironmentMap environment,
+ const struct passwd* pwinfo) {
+ // By convention, a login shell is signified by preceeding the shell name in
+ // argv[0] with a '-'.
+ std::string shell_name =
+ '-' + base::FilePath(pwinfo->pw_shell).BaseName().value();
+
+ base::Optional<std::string> escaped_script_path =
+ ShellEscapeArgument(FindScriptPath());
+ CHECK(escaped_script_path) << "Could not escape script path";
+
+ std::string shell_arg =
+ *escaped_script_path + " --start --foreground --keep-parent-env";
+
+ environment["USER"] = pwinfo->pw_name;
+ environment["LOGNAME"] = pwinfo->pw_name;
+ environment["HOME"] = pwinfo->pw_dir;
+ environment["SHELL"] = pwinfo->pw_shell;
+ if (!environment.count("PATH")) {
+ environment["PATH"] = "/bin:/usr/bin";
+ }
+
+ std::vector<std::string> env_strings;
+ for (const auto& env_var : environment) {
+ env_strings.emplace_back(env_var.first + "=" + env_var.second);
+ }
+
+ std::vector<const char*> arg_ptrs = {shell_name.c_str(), "-c",
+ shell_arg.c_str(), nullptr};
+ std::vector<const char*> env_ptrs;
+ env_ptrs.reserve(env_strings.size() + 1);
+ for (const auto& env_string : env_strings) {
+ env_ptrs.push_back(env_string.c_str());
+ }
+ env_ptrs.push_back(nullptr);
+
+ execve(pwinfo->pw_shell, const_cast<char* const*>(arg_ptrs.data()),
+ const_cast<char* const*>(env_ptrs.data()));
+ PLOG(FATAL) << "Failed to exec login shell " << pwinfo->pw_shell;
+}
+
// 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.
-void ExecuteSession(std::string user, base::StringPiece script_path,
- bool chown_log) {
- //////////////////////////////////////////////////////////////////////////////
- // Set up the PAM session
- //////////////////////////////////////////////////////////////////////////////
-
+// 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.
+void ExecuteSession(std::string user,
+ bool chown_log,
+ base::Optional<uid_t> match_uid) {
PamHandle pam_handle(kPamName, user.c_str(), &kPamConversation);
CHECK(pam_handle.IsInitialized()) << "Failed to initialize PAM";
@@ -285,101 +334,57 @@ void ExecuteSession(std::string user, base::StringPiece script_path,
// The above may have remapped the user.
user = pam_handle.GetUser().value_or(std::move(user));
- base::Optional<base::EnvironmentMap> pam_environment =
- pam_handle.GetEnvironment();
- CHECK(pam_environment) << "Failed to get environment from PAM";
-
- //////////////////////////////////////////////////////////////////////////////
- // Prepare to execute remoting session process
- //////////////////////////////////////////////////////////////////////////////
-
- // Callback to be run in child process after fork and before exec.
- // chdir is called manually instead of using LaunchOptions.current_directory
- // because it should take place after setuid. (This both makes sure the user
- // has the proper permissions and also apparently avoids some obscure errors
- // that can occur when accessing some network filesystems as the wrong user.)
- class PreExecDelegate : public base::LaunchOptions::PreExecDelegate {
- public:
- void RunAsyncSafe() override {
- // Use RAW_CHECK to avoid allocating post-fork.
- RAW_CHECK(setuid(pwinfo_->pw_uid) == 0);
- RAW_CHECK(chdir(pwinfo_->pw_dir) == 0);
- }
-
- PreExecDelegate(struct passwd* pwinfo) : pwinfo_(pwinfo) {}
-
- private:
- struct passwd* pwinfo_;
- };
-
// Fetch pwinfo again, as it may have been invalidated or the user name might
// have been remapped.
pwinfo = getpwnam(user.c_str());
PCHECK(pwinfo != nullptr) << "getpwnam failed";
+ if (match_uid && pwinfo->pw_uid != *match_uid) {
+ LOG(FATAL) << "PAM remapped username to one with a different user ID.";
+ }
+
if (chown_log) {
int result = fchown(STDOUT_FILENO, pwinfo->pw_uid, pwinfo->pw_gid);
PLOG_IF(WARNING, result != 0) << "Failed to change log file owner";
}
- PreExecDelegate pre_exec_delegate(pwinfo);
-
- base::LaunchOptions launch_options;
-
- // Required to allow suid binaries to function in the session.
- launch_options.allow_new_privs = true;
-
- launch_options.kill_on_parent_death = true;
-
- launch_options.clear_environ = true;
- launch_options.environ = std::move(*pam_environment);
- launch_options.environ["USER"] = pwinfo->pw_name;
- launch_options.environ["LOGNAME"] = pwinfo->pw_name;
- launch_options.environ["HOME"] = pwinfo->pw_dir;
- launch_options.environ["SHELL"] = pwinfo->pw_shell;
- if (!launch_options.environ.count("PATH")) {
- launch_options.environ["PATH"] = "/bin:/usr/bin";
- }
-
- launch_options.pre_exec_delegate = &pre_exec_delegate;
-
- // By convention, a login shell is signified by preceeding the shell name in
- // argv[0] with a '-'.
- base::CommandLine command_line(base::FilePath(
- '-' + base::FilePath(pwinfo->pw_shell).BaseName().value()));
-
- base::Optional<std::string> escaped_script_path =
- ShellEscapeArgument(script_path);
-
- CHECK(escaped_script_path) << "Could not escape script path";
-
- command_line.AppendSwitch("-c");
- command_line.AppendArg(*escaped_script_path +
- " --start --foreground --keep-parent-env");
-
- // Tell LaunchProcess where to find the executable, since argv[0] doesn't
- // point to it.
- launch_options.real_path = base::FilePath(pwinfo->pw_shell);
-
- //////////////////////////////////////////////////////////////////////////////
- // We're ready to execute the remoting session
- //////////////////////////////////////////////////////////////////////////////
-
- base::Process child = base::LaunchProcess(command_line, launch_options);
-
- if (child.IsValid()) {
- int exit_code = 0;
- // Die if wait fails so we don't close the PAM session while the child is
- // still running.
- CHECK(child.WaitForExit(&exit_code)) << "Failed to wait for child process";
- LOG_IF(WARNING, exit_code != 0) << "Child did not exit normally";
- }
+ pid_t child_pid = fork();
+ PCHECK(child_pid >= 0) << "fork failed";
+ if (child_pid == 0) {
+ PCHECK(setuid(pwinfo->pw_uid) == 0) << "setuid failed";
+ PCHECK(chdir(pwinfo->pw_dir) == 0) << "chdir to $HOME failed";
+ base::Optional<base::EnvironmentMap> pam_environment =
+ pam_handle.GetEnvironment();
+ CHECK(pam_environment) << "Failed to get environment from PAM";
+ ExecMe2MeScript(std::move(*pam_environment), pwinfo); // Never returns
+ } else {
+ // waitpid will return if the child is ptraced, so loop until the process
+ // actually exits.
+ int status;
+ do {
+ pid_t wait_result = waitpid(child_pid, &status, 0);
+
+ // Die if wait fails so we don't close the PAM session while the child is
+ // still running.
+ PCHECK(wait_result >= 0) << "wait failed";
+ } while (!WIFEXITED(status) && !WIFSIGNALED(status));
+
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == EXIT_SUCCESS) {
+ LOG(INFO) << "Child exited successfully";
+ } else {
+ LOG(WARNING) << "Child exited with status " << WEXITSTATUS(status);
+ }
+ } else if (WIFSIGNALED(status)) {
+ LOG(WARNING) << "Child terminated by signal " << WTERMSIG(status);
+ }
- // Best effort PAM cleanup
- if (pam_handle.CloseSession(0) != PAM_SUCCESS) {
- LOG(WARNING) << "Failed to close PAM session";
+ // Best effort PAM cleanup
+ if (pam_handle.CloseSession(0) != PAM_SUCCESS) {
+ LOG(WARNING) << "Failed to close PAM session";
+ }
+ ignore_result(pam_handle.SetCredentials(PAM_DELETE_CRED));
}
- ignore_result(pam_handle.SetCredentials(PAM_DELETE_CRED));
}
// Opens a temp file for logging. Exits the program on failure.
@@ -400,6 +405,33 @@ int OpenLogFile() {
return fd;
}
+// 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() {
+ uid_t real_uid = getuid();
+ struct passwd* pwinfo;
+ for (const char* var : {"USER", "LOGNAME"}) {
+ const char* value = getenv(var);
+ if (value) {
+ pwinfo = getpwnam(value);
+ // USER and LOGNAME can be overridden, so make sure the value is valid
+ // and matches the UID of the invoking user.
+ if (pwinfo && pwinfo->pw_uid == real_uid) {
+ return pwinfo->pw_name;
+ }
+ }
+ }
+ errno = 0;
+ pwinfo = getpwuid(real_uid);
+ PCHECK(pwinfo) << "getpwuid failed";
+ return pwinfo->pw_name;
+}
+
// Daemonizes the process. Output is redirected to a file. Exits the program on
// failure.
//
@@ -411,7 +443,6 @@ int OpenLogFile() {
// it turns out to be desired, it can be implemented by setting up a pipe and
// passing a file descriptor to the Python script.
void Daemonize() {
-
int log_fd = OpenLogFile();
int devnull_fd = open("/dev/null", O_RDONLY);
PCHECK(devnull_fd != -1);
@@ -456,34 +487,54 @@ void Daemonize() {
} // namespace
int main(int argc, char** argv) {
- base::CommandLine::Init(argc, argv);
-
- const base::CommandLine* command_line =
- base::CommandLine::ForCurrentProcess();
- if (command_line->HasSwitch(kHelpSwitchName) ||
- command_line->HasSwitch(kQuestionSwitchName)) {
- PrintUsage(command_line->GetProgram());
- std::exit(EXIT_SUCCESS);
+ if (argc < 2 || strcmp(argv[1], "start") != 0) {
+ PrintUsage();
+ std::exit(EXIT_FAILURE);
}
- base::FilePath script_path =
- command_line->GetSwitchValuePath(kScriptSwitchName);
- std::string user = command_line->GetSwitchValueNative(kUserSwitchName);
- bool foreground = command_line->HasSwitch(kForegroundSwitchName);
+ // Skip initial args
+ argc -= 2;
+ argv += 2;
- if (script_path.empty()) {
- std::fputs("The path to the me2me python script is required.\n", stderr);
- std::exit(EXIT_FAILURE);
+ bool foreground = false;
+ if (argc >= 1 && strcmp(argv[0], kForegroundFlag) == 0) {
+ foreground = true;
+ argc -= 1;
+ argv += 1;
}
- if (user.empty()) {
- std::fputs("The target user must be specified.\n", stderr);
+ if (argc > 1) {
+ std::fputs("Too many command-line arguments.\n", stderr);
std::exit(EXIT_FAILURE);
}
+ uid_t real_uid = getuid();
+ std::string user;
+
+ // Note: This logic is security sensitive. It is imperative that a non-root
+ // user is not allowed to specify an arbitrary target user.
+ if (argc == 0) {
+ if (real_uid == 0) {
+ std::fputs("Target user must be specified when run as root.\n", stderr);
+ std::exit(EXIT_FAILURE);
+ }
+ user = FindCurrentUsername();
+ } else {
+ if (real_uid != 0) {
+ std::fputs("Target user may not be specified by non-root users.\n",
+ stderr);
+ std::exit(EXIT_FAILURE);
+ }
+ user = argv[0];
+ }
+
if (!foreground) {
Daemonize();
}
- ExecuteSession(std::move(user), script_path.value(), !foreground);
+ // Daemonizing redirects stdout to a log file, which we want to be owned by
+ // the target user.
+ bool chown_stdout = !foreground;
+ ExecuteSession(std::move(user), chown_stdout,
+ real_uid != 0 ? base::make_optional(real_uid) : base::nullopt);
}
« no previous file with comments | « remoting/host/installer/linux/debian/chrome-remote-desktop.init ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698