Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 // | 4 // |
| 5 // This file implements a wrapper to run the virtual me2me session within a | 5 // This file implements a wrapper to run the virtual me2me session within a |
| 6 // proper PAM session. It will generally be run as root and drop privileges to | 6 // proper PAM session. It will generally be run as root and drop privileges to |
| 7 // the specified user before running the me2me session script. | 7 // the specified user before running the me2me session script. |
| 8 | 8 |
| 9 // Usage: user-session start [--foreground] [user] | |
| 10 // | |
| 11 // Options: | |
| 12 // --foreground - Don't doemonize. | |
|
Jamie
2017/07/20 16:56:48
daemonize
rkjnsn
2017/07/20 17:39:03
Acknowledged.
| |
| 13 // user - Create a session for the specified user. Required when | |
| 14 // running as root, not allowed when running as a normal user. | |
| 15 | |
| 9 #include <sys/types.h> | 16 #include <sys/types.h> |
| 10 #include <sys/stat.h> | 17 #include <sys/stat.h> |
| 11 #include <sys/wait.h> | 18 #include <sys/wait.h> |
| 12 #include <fcntl.h> | 19 #include <fcntl.h> |
| 13 #include <grp.h> | 20 #include <grp.h> |
| 21 #include <limits.h> | |
| 14 #include <pwd.h> | 22 #include <pwd.h> |
| 15 #include <unistd.h> | 23 #include <unistd.h> |
| 16 | 24 |
| 17 #include <cerrno> | 25 #include <cerrno> |
| 18 #include <cstdio> | 26 #include <cstdio> |
| 19 #include <cstdlib> | 27 #include <cstdlib> |
| 20 #include <cstring> | 28 #include <cstring> |
| 21 #include <ctime> | 29 #include <ctime> |
| 22 | 30 |
| 23 #include <map> | 31 #include <map> |
| 24 #include <memory> | 32 #include <memory> |
| 25 #include <string> | 33 #include <string> |
| 26 #include <tuple> | 34 #include <tuple> |
| 27 #include <utility> | 35 #include <utility> |
| 28 #include <vector> | 36 #include <vector> |
| 29 | 37 |
| 30 #include <security/pam_appl.h> | 38 #include <security/pam_appl.h> |
| 31 | 39 |
| 32 #include "base/command_line.h" | |
| 33 #include "base/environment.h" | 40 #include "base/environment.h" |
| 34 #include "base/files/file_path.h" | 41 #include "base/files/file_path.h" |
| 35 #include "base/logging.h" | 42 #include "base/logging.h" |
| 36 #include "base/macros.h" | 43 #include "base/macros.h" |
| 37 #include "base/optional.h" | 44 #include "base/optional.h" |
| 38 #include "base/process/launch.h" | 45 #include "base/process/launch.h" |
| 39 #include "base/strings/string_piece.h" | 46 #include "base/strings/string_piece.h" |
| 40 | 47 |
| 41 namespace { | 48 namespace { |
| 42 | 49 |
| 43 const char kPamName[] = "chrome-remote-desktop"; | 50 const char kPamName[] = "chrome-remote-desktop"; |
| 44 | 51 const char kScriptName[] = "chrome-remote-desktop"; |
| 45 const char kHelpSwitchName[] = "help"; | 52 const char kForegroundFlag[] = "--foreground"; |
| 46 const char kQuestionSwitchName[] = "?"; | |
| 47 const char kUserSwitchName[] = "user"; | |
| 48 const char kScriptSwitchName[] = "me2me-script"; | |
| 49 const char kForegroundSwitchName[] = "foreground"; | |
| 50 | 53 |
| 51 // This template will be formatted by strftime and then used by mkstemp | 54 // This template will be formatted by strftime and then used by mkstemp |
| 52 const char kLogFileTemplate[] = | 55 const char kLogFileTemplate[] = |
| 53 "/tmp/chrome_remote_desktop_%Y%m%d_%H%M%S_XXXXXX"; | 56 "/tmp/chrome_remote_desktop_%Y%m%d_%H%M%S_XXXXXX"; |
| 54 | 57 |
| 55 const char kUsageMessage[] = | 58 const char kUsageMessage[] = |
| 56 "Usage: %s [options]\n" | 59 "This program is not intended to be run by end users. To configure Chrome\n" |
| 57 "\n" | 60 "Remote Desktop, please install the app from the Chrome Web Store:\n" |
| 58 "Options:\n" | 61 "https://chrome.google.com/remotedesktop\n"; |
| 59 " --help, -? - Print this message.\n" | |
| 60 " --user=<user> - Create session as the specified user. " | |
| 61 "(Must run as root.)\n" | |
| 62 " --me2me-script=<script> - Location of the me2me python script " | |
| 63 "(required)\n" | |
| 64 " --foreground - Don't daemonize.\n"; | |
| 65 | 62 |
| 66 void PrintUsage(const base::FilePath& program_name) { | 63 void PrintUsage() { |
| 67 std::printf(kUsageMessage, program_name.MaybeAsASCII().c_str()); | 64 std::fputs(kUsageMessage, stderr); |
| 68 } | 65 } |
| 69 | 66 |
| 70 // Shell-escapes a single argument in a way that is compatible with various | 67 // Shell-escapes a single argument in a way that is compatible with various |
| 71 // different shells. Returns nullopt when argument contains a newline, which | 68 // different shells. Returns nullopt when argument contains a newline, which |
| 72 // can't be represented in a cross-shell fashion. | 69 // can't be represented in a cross-shell fashion. |
| 73 base::Optional<std::string> ShellEscapeArgument( | 70 base::Optional<std::string> ShellEscapeArgument( |
| 74 const base::StringPiece argument) { | 71 const base::StringPiece argument) { |
| 75 std::string result; | 72 std::string result; |
| 76 for (char character : argument) { | 73 for (char character : argument) { |
| 77 // csh in particular doesn't provide a good way to handle this | 74 // csh in particular doesn't provide a good way to handle this |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 236 } | 233 } |
| 237 } | 234 } |
| 238 | 235 |
| 239 private: | 236 private: |
| 240 pam_handle_t* pam_handle_ = nullptr; | 237 pam_handle_t* pam_handle_ = nullptr; |
| 241 int last_return_code_ = PAM_SUCCESS; | 238 int last_return_code_ = PAM_SUCCESS; |
| 242 | 239 |
| 243 DISALLOW_COPY_AND_ASSIGN(PamHandle); | 240 DISALLOW_COPY_AND_ASSIGN(PamHandle); |
| 244 }; | 241 }; |
| 245 | 242 |
| 243 std::string FindScriptPath() { | |
| 244 std::string script_path(PATH_MAX, '\0'); | |
| 245 ssize_t readlink_result; | |
| 246 | |
| 247 readlink_result = | |
| 248 readlink("/proc/self/exe", &script_path[0], script_path.size()); | |
|
Lambros
2017/07/20 01:42:49
Use base::ReadSymbolicLink() from base/files/file_
Jamie
2017/07/20 02:06:26
This probably applies to a few of your comments, a
rkjnsn
2017/07/20 06:45:52
When I was developing this patch, this happened be
rkjnsn
2017/07/20 17:49:43
Actually, looking at the implementation in file_ut
rkjnsn
2017/07/21 00:44:25
Okay, further testing indicates that Linux will re
| |
| 249 PCHECK(readlink_result >= 0) << "readlink failed"; | |
|
Jamie
2017/07/20 16:56:48
Why PCHECK instead of CHECK here? I couldn't find
rkjnsn
2017/07/20 17:39:03
PCHECK additionally prints the error message assoc
| |
| 250 CHECK(static_cast<std::size_t>(readlink_result) < script_path.size()) | |
| 251 << "binary path too long"; | |
| 252 CHECK(script_path[0] == '/') << "path not absolute"; | |
| 253 | |
| 254 script_path.resize(readlink_result); | |
| 255 | |
| 256 std::size_t last_slash = script_path.find_last_of('/'); | |
|
Lambros
2017/07/20 01:42:49
Use base::FilePath:: DirName() and Append() instea
rkjnsn
2017/07/20 06:45:51
Same here.
| |
| 257 script_path.replace(script_path.begin() + last_slash + 1, script_path.end(), | |
| 258 kScriptName); | |
| 259 return script_path; | |
| 260 } | |
| 261 | |
| 262 // Execs the me2me script. | |
| 263 // This function is called after forking and dropping privileges. It never | |
| 264 // returns. | |
| 265 void ChildProcess(base::EnvironmentMap environment, | |
|
Lambros
2017/07/20 01:42:49
Please name the function as a verb.
rkjnsn
2017/07/20 06:45:51
Acknowledged.
| |
| 266 const struct passwd* pwinfo) { | |
| 267 // By convention, a login shell is signified by preceeding the shell name in | |
| 268 // argv[0] with a '-'. | |
| 269 std::string shell_name = | |
| 270 '-' + base::FilePath(pwinfo->pw_shell).BaseName().value(); | |
| 271 | |
| 272 base::Optional<std::string> escaped_script_path = | |
| 273 ShellEscapeArgument(FindScriptPath()); | |
| 274 CHECK(escaped_script_path) << "Could not escape script path"; | |
| 275 | |
| 276 std::string shell_arg = | |
| 277 *escaped_script_path + " --start --foreground --keep-parent-env"; | |
| 278 | |
| 279 environment["USER"] = pwinfo->pw_name; | |
| 280 environment["LOGNAME"] = pwinfo->pw_name; | |
| 281 environment["HOME"] = pwinfo->pw_dir; | |
| 282 environment["SHELL"] = pwinfo->pw_shell; | |
| 283 if (!environment.count("PATH")) { | |
| 284 environment["PATH"] = "/bin:/usr/bin"; | |
| 285 } | |
| 286 | |
| 287 std::vector<std::string> env_strings; | |
| 288 for (auto& env_var : environment) { | |
|
Lambros
2017/07/20 01:42:49
const auto&
rkjnsn
2017/07/20 06:45:52
Acknowledged.
| |
| 289 env_strings.emplace_back(env_var.first + "=" + env_var.second); | |
| 290 } | |
| 291 | |
| 292 std::vector<const char*> arg_ptrs = {shell_name.c_str(), "-c", | |
| 293 shell_arg.c_str(), nullptr}; | |
| 294 std::vector<const char*> env_ptrs; | |
| 295 env_ptrs.reserve(env_strings.size() + 1); | |
|
Lambros
2017/07/20 01:42:49
I don't know if we're allowed to malloc() in the c
rkjnsn
2017/07/20 06:45:52
I think this should be okay. As far as I understan
| |
| 296 for (auto& env_string : env_strings) { | |
|
Lambros
2017/07/20 01:42:50
const auto&
rkjnsn
2017/07/20 06:45:52
Acknowledged.
| |
| 297 env_ptrs.push_back(env_string.c_str()); | |
| 298 } | |
| 299 env_ptrs.push_back(nullptr); | |
| 300 | |
| 301 execve(pwinfo->pw_shell, const_cast<char* const*>(arg_ptrs.data()), | |
| 302 const_cast<char* const*>(env_ptrs.data())); | |
| 303 PCHECK(false) << "failed to exec session script"; | |
|
Lambros
2017/07/20 01:42:49
Maybe log the filename (full path) of the script t
rkjnsn
2017/07/20 06:45:51
Acknowledged.
| |
| 304 } | |
| 305 | |
| 246 // Runs the me2me script in a PAM session. Exits the program on failure. | 306 // Runs the me2me script in a PAM session. Exits the program on failure. |
| 247 // If chown_log is true, the owner and group of the file associated with stdout | 307 // If chown_log is true, the owner and group of the file associated with stdout |
| 248 // will be changed to the target user. | 308 // will be changed to the target user. If match_uid is not nullopt, this |
|
Lambros
2017/07/20 01:42:49
I think "present" or "provided" reads better than
rkjnsn
2017/07/20 06:45:51
Agreed.
| |
| 249 void ExecuteSession(std::string user, base::StringPiece script_path, | 309 // function will fail if the final user id does not match the one provided. |
| 250 bool chown_log) { | 310 void ExecuteSession(std::string user, |
| 251 ////////////////////////////////////////////////////////////////////////////// | 311 bool chown_log, |
| 252 // Set up the PAM session | 312 base::Optional<uid_t> match_uid) { |
| 253 ////////////////////////////////////////////////////////////////////////////// | |
| 254 | |
| 255 PamHandle pam_handle(kPamName, user.c_str(), &kPamConversation); | 313 PamHandle pam_handle(kPamName, user.c_str(), &kPamConversation); |
| 256 CHECK(pam_handle.IsInitialized()) << "Failed to initialize PAM"; | 314 CHECK(pam_handle.IsInitialized()) << "Failed to initialize PAM"; |
| 257 | 315 |
| 258 // Make sure the account is valid and enabled. | 316 // Make sure the account is valid and enabled. |
| 259 pam_handle.CheckReturnCode(pam_handle.AccountManagement(0), "Account check"); | 317 pam_handle.CheckReturnCode(pam_handle.AccountManagement(0), "Account check"); |
| 260 | 318 |
| 261 // PAM may remap the user at any stage. | 319 // PAM may remap the user at any stage. |
| 262 user = pam_handle.GetUser().value_or(std::move(user)); | 320 user = pam_handle.GetUser().value_or(std::move(user)); |
| 263 | 321 |
| 264 // setcred explicitly does not handle user id or group membership, and | 322 // setcred explicitly does not handle user id or group membership, and |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 278 // as done here, but it may be worth noting that `login` calls open_session | 336 // as done here, but it may be worth noting that `login` calls open_session |
| 279 // first. | 337 // first. |
| 280 pam_handle.CheckReturnCode(pam_handle.SetCredentials(PAM_ESTABLISH_CRED), | 338 pam_handle.CheckReturnCode(pam_handle.SetCredentials(PAM_ESTABLISH_CRED), |
| 281 "Set credentials"); | 339 "Set credentials"); |
| 282 | 340 |
| 283 pam_handle.CheckReturnCode(pam_handle.OpenSession(0), "Open session"); | 341 pam_handle.CheckReturnCode(pam_handle.OpenSession(0), "Open session"); |
| 284 | 342 |
| 285 // The above may have remapped the user. | 343 // The above may have remapped the user. |
| 286 user = pam_handle.GetUser().value_or(std::move(user)); | 344 user = pam_handle.GetUser().value_or(std::move(user)); |
| 287 | 345 |
| 288 base::Optional<base::EnvironmentMap> pam_environment = | |
| 289 pam_handle.GetEnvironment(); | |
| 290 CHECK(pam_environment) << "Failed to get environment from PAM"; | |
| 291 | |
| 292 ////////////////////////////////////////////////////////////////////////////// | |
| 293 // Prepare to execute remoting session process | |
| 294 ////////////////////////////////////////////////////////////////////////////// | |
| 295 | |
| 296 // Callback to be run in child process after fork and before exec. | |
| 297 // chdir is called manually instead of using LaunchOptions.current_directory | |
| 298 // because it should take place after setuid. (This both makes sure the user | |
| 299 // has the proper permissions and also apparently avoids some obscure errors | |
| 300 // that can occur when accessing some network filesystems as the wrong user.) | |
| 301 class PreExecDelegate : public base::LaunchOptions::PreExecDelegate { | |
| 302 public: | |
| 303 void RunAsyncSafe() override { | |
| 304 // Use RAW_CHECK to avoid allocating post-fork. | |
| 305 RAW_CHECK(setuid(pwinfo_->pw_uid) == 0); | |
| 306 RAW_CHECK(chdir(pwinfo_->pw_dir) == 0); | |
| 307 } | |
| 308 | |
| 309 PreExecDelegate(struct passwd* pwinfo) : pwinfo_(pwinfo) {} | |
| 310 | |
| 311 private: | |
| 312 struct passwd* pwinfo_; | |
| 313 }; | |
| 314 | |
| 315 // Fetch pwinfo again, as it may have been invalidated or the user name might | 346 // Fetch pwinfo again, as it may have been invalidated or the user name might |
| 316 // have been remapped. | 347 // have been remapped. |
| 317 pwinfo = getpwnam(user.c_str()); | 348 pwinfo = getpwnam(user.c_str()); |
| 318 PCHECK(pwinfo != nullptr) << "getpwnam failed"; | 349 PCHECK(pwinfo != nullptr) << "getpwnam failed"; |
| 319 | 350 |
| 351 if (match_uid && pwinfo->pw_uid != *match_uid) { | |
| 352 LOG(FATAL) << "PAM remapped username to one with a different user ID."; | |
| 353 } | |
| 354 | |
| 320 if (chown_log) { | 355 if (chown_log) { |
| 321 int result = fchown(STDOUT_FILENO, pwinfo->pw_uid, pwinfo->pw_gid); | 356 int result = fchown(STDOUT_FILENO, pwinfo->pw_uid, pwinfo->pw_gid); |
| 322 PLOG_IF(WARNING, result != 0) << "Failed to change log file owner"; | 357 PLOG_IF(WARNING, result != 0) << "Failed to change log file owner"; |
| 323 } | 358 } |
| 324 | 359 |
| 325 PreExecDelegate pre_exec_delegate(pwinfo); | 360 pid_t child_pid = fork(); |
|
Lambros
2017/07/20 01:42:49
Why have you removed base::LaunchProcess() here?
rkjnsn
2017/07/20 06:45:52
This is actually the key change for this version o
| |
| 361 PCHECK(child_pid >= 0) << "fork failed"; | |
| 362 if (child_pid == 0) { | |
| 363 PCHECK(setuid(pwinfo->pw_uid) == 0) << "setuid failed"; | |
| 364 PCHECK(chdir(pwinfo->pw_dir) == 0) << "chdir to $HOME failed"; | |
| 365 base::Optional<base::EnvironmentMap> pam_environment = | |
| 366 pam_handle.GetEnvironment(); | |
| 367 CHECK(pam_environment) << "Failed to get environment from PAM"; | |
| 368 ChildProcess(std::move(*pam_environment), pwinfo); // Never returns | |
| 369 } else { | |
| 370 // waidpid will return if the child is ptraced, so loop until the process | |
|
Lambros
2017/07/20 01:42:49
waitpid
rkjnsn
2017/07/20 06:45:51
Acknowledged.
| |
| 371 // actually exits. | |
|
Lambros
2017/07/20 01:42:49
Could this be a busy loop? Have a look at
https://
rkjnsn
2017/07/20 06:45:52
I don't think so. In that code, they need to sleep
| |
| 372 int status; | |
| 373 do { | |
| 374 pid_t wait_result = waitpid(child_pid, &status, 0); | |
| 326 | 375 |
| 327 base::LaunchOptions launch_options; | 376 // Die if wait fails so we don't close the PAM session while the child is |
| 377 // still running. | |
| 378 PCHECK(wait_result >= 0) << "wait failed"; | |
| 379 } while (!WIFEXITED(status) && !WIFSIGNALED(status)); | |
| 328 | 380 |
| 329 // Required to allow suid binaries to function in the session. | 381 if (WIFEXITED(status)) { |
| 330 launch_options.allow_new_privs = true; | 382 if (WEXITSTATUS(status) == EXIT_SUCCESS) { |
| 383 LOG(INFO) << "Child exited successfully"; | |
| 384 } else { | |
| 385 LOG(WARNING) << "Child exited with status " << WEXITSTATUS(status); | |
| 386 } | |
| 387 } else if (WIFSIGNALED(status)) { | |
| 388 LOG(WARNING) << "Child terminated by signal " << WTERMSIG(status); | |
| 389 } | |
| 331 | 390 |
| 332 launch_options.kill_on_parent_death = true; | 391 // Best effort PAM cleanup |
| 333 | 392 if (pam_handle.CloseSession(0) != PAM_SUCCESS) { |
| 334 launch_options.clear_environ = true; | 393 LOG(WARNING) << "Failed to close PAM session"; |
| 335 launch_options.environ = std::move(*pam_environment); | 394 } |
| 336 launch_options.environ["USER"] = pwinfo->pw_name; | 395 ignore_result(pam_handle.SetCredentials(PAM_DELETE_CRED)); |
| 337 launch_options.environ["LOGNAME"] = pwinfo->pw_name; | |
| 338 launch_options.environ["HOME"] = pwinfo->pw_dir; | |
| 339 launch_options.environ["SHELL"] = pwinfo->pw_shell; | |
| 340 if (!launch_options.environ.count("PATH")) { | |
| 341 launch_options.environ["PATH"] = "/bin:/usr/bin"; | |
| 342 } | 396 } |
| 343 | |
| 344 launch_options.pre_exec_delegate = &pre_exec_delegate; | |
| 345 | |
| 346 // By convention, a login shell is signified by preceeding the shell name in | |
| 347 // argv[0] with a '-'. | |
| 348 base::CommandLine command_line(base::FilePath( | |
| 349 '-' + base::FilePath(pwinfo->pw_shell).BaseName().value())); | |
| 350 | |
| 351 base::Optional<std::string> escaped_script_path = | |
| 352 ShellEscapeArgument(script_path); | |
| 353 | |
| 354 CHECK(escaped_script_path) << "Could not escape script path"; | |
| 355 | |
| 356 command_line.AppendSwitch("-c"); | |
| 357 command_line.AppendArg(*escaped_script_path + | |
| 358 " --start --foreground --keep-parent-env"); | |
| 359 | |
| 360 // Tell LaunchProcess where to find the executable, since argv[0] doesn't | |
| 361 // point to it. | |
| 362 launch_options.real_path = base::FilePath(pwinfo->pw_shell); | |
| 363 | |
| 364 ////////////////////////////////////////////////////////////////////////////// | |
| 365 // We're ready to execute the remoting session | |
| 366 ////////////////////////////////////////////////////////////////////////////// | |
| 367 | |
| 368 base::Process child = base::LaunchProcess(command_line, launch_options); | |
| 369 | |
| 370 if (child.IsValid()) { | |
| 371 int exit_code = 0; | |
| 372 // Die if wait fails so we don't close the PAM session while the child is | |
| 373 // still running. | |
| 374 CHECK(child.WaitForExit(&exit_code)) << "Failed to wait for child process"; | |
| 375 LOG_IF(WARNING, exit_code != 0) << "Child did not exit normally"; | |
| 376 } | |
| 377 | |
| 378 // Best effort PAM cleanup | |
| 379 if (pam_handle.CloseSession(0) != PAM_SUCCESS) { | |
| 380 LOG(WARNING) << "Failed to close PAM session"; | |
| 381 } | |
| 382 ignore_result(pam_handle.SetCredentials(PAM_DELETE_CRED)); | |
| 383 } | 397 } |
| 384 | 398 |
| 385 // Opens a temp file for logging. Exits the program on failure. | 399 // Opens a temp file for logging. Exits the program on failure. |
| 386 int OpenLogFile() { | 400 int OpenLogFile() { |
| 387 char logfile[265]; | 401 char logfile[265]; |
| 388 std::time_t time = std::time(nullptr); | 402 std::time_t time = std::time(nullptr); |
| 389 CHECK_NE(time, (std::time_t)(-1)); | 403 CHECK_NE(time, (std::time_t)(-1)); |
| 390 // Safe because we're single threaded | 404 // Safe because we're single threaded |
| 391 std::tm* localtime = std::localtime(&time); | 405 std::tm* localtime = std::localtime(&time); |
| 392 CHECK_NE(std::strftime(logfile, sizeof(logfile), kLogFileTemplate, localtime), | 406 CHECK_NE(std::strftime(logfile, sizeof(logfile), kLogFileTemplate, localtime), |
| 393 (std::size_t) 0); | 407 (std::size_t) 0); |
| 394 | 408 |
| 395 mode_t mode = umask(0177); | 409 mode_t mode = umask(0177); |
| 396 int fd = mkstemp(logfile); | 410 int fd = mkstemp(logfile); |
| 397 PCHECK(fd != -1); | 411 PCHECK(fd != -1); |
| 398 umask(mode); | 412 umask(mode); |
| 399 | 413 |
| 400 return fd; | 414 return fd; |
| 401 } | 415 } |
| 402 | 416 |
| 417 // Find the username for the current user. If either USER or LOGNAME is set to | |
| 418 // a user matching our real user id, we return that. Otherwise, we use getpwuid | |
| 419 // to attempt a reverse lookup. Note: It's possible to more than user with the | |
|
Lambros
2017/07/20 01:42:50
.. to have more ..
Is this really true? I thought
rkjnsn
2017/07/20 06:45:52
Yes, I should probably word this differently. Havi
| |
| 420 // same user id but different group membership, home directories, et cetera, | |
| 421 // which is why we check USER and LOGNAME first. | |
| 422 std::string FindCurrentUsername() { | |
| 423 uid_t real_uid = getuid(); | |
| 424 struct passwd* pwinfo; | |
| 425 for (const char* var : {"USER", "LOGNAME"}) { | |
| 426 const char* value = getenv(var); | |
| 427 if (value) { | |
| 428 pwinfo = getpwnam(value); | |
| 429 // USER and LOGNAME can be overridden, so make sure the value is valid | |
| 430 // and matches the UID of the invoking user. | |
| 431 if (pwinfo && pwinfo->pw_uid == real_uid) { | |
| 432 return pwinfo->pw_name; | |
| 433 } | |
| 434 } | |
| 435 } | |
| 436 errno = 0; | |
| 437 pwinfo = getpwuid(real_uid); | |
| 438 PCHECK(pwinfo) << "getpwuid failed"; | |
| 439 return pwinfo->pw_name; | |
| 440 } | |
| 441 | |
| 403 // Daemonizes the process. Output is redirected to a file. Exits the program on | 442 // Daemonizes the process. Output is redirected to a file. Exits the program on |
| 404 // failure. | 443 // failure. |
| 405 // | 444 // |
| 406 // This logic is mostly the same as daemonize() in linux_me2me_host.py. Log- | 445 // This logic is mostly the same as daemonize() in linux_me2me_host.py. Log- |
| 407 // file redirection especially should be kept in sync. Note that this does | 446 // file redirection especially should be kept in sync. Note that this does |
| 408 // not currently wait for the host to start successfully before exiting the | 447 // not currently wait for the host to start successfully before exiting the |
| 409 // parent process like the Python script does, as that functionality is | 448 // parent process like the Python script does, as that functionality is |
| 410 // probably not useful at boot, where the wrapper is expected to be used. If | 449 // probably not useful at boot, where the wrapper is expected to be used. If |
| 411 // it turns out to be desired, it can be implemented by setting up a pipe and | 450 // it turns out to be desired, it can be implemented by setting up a pipe and |
| 412 // passing a file descriptor to the Python script. | 451 // passing a file descriptor to the Python script. |
| 413 void Daemonize() { | 452 void Daemonize() { |
| 414 | |
| 415 int log_fd = OpenLogFile(); | 453 int log_fd = OpenLogFile(); |
| 416 int devnull_fd = open("/dev/null", O_RDONLY); | 454 int devnull_fd = open("/dev/null", O_RDONLY); |
| 417 PCHECK(devnull_fd != -1); | 455 PCHECK(devnull_fd != -1); |
| 418 | 456 |
| 419 PCHECK(dup2(devnull_fd, STDIN_FILENO) != -1); | 457 PCHECK(dup2(devnull_fd, STDIN_FILENO) != -1); |
| 420 PCHECK(dup2(log_fd, STDOUT_FILENO) != -1); | 458 PCHECK(dup2(log_fd, STDOUT_FILENO) != -1); |
| 421 PCHECK(dup2(log_fd, STDERR_FILENO) != -1); | 459 PCHECK(dup2(log_fd, STDERR_FILENO) != -1); |
| 422 | 460 |
| 423 // Close all file descriptors except stdio, including any we may have | 461 // Close all file descriptors except stdio, including any we may have |
| 424 // inherited. | 462 // inherited. |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 449 // dropped privileges, so change to / to make sure we're not keeping any other | 487 // dropped privileges, so change to / to make sure we're not keeping any other |
| 450 // directory in use. | 488 // directory in use. |
| 451 PCHECK(chdir("/") == 0); | 489 PCHECK(chdir("/") == 0); |
| 452 | 490 |
| 453 // Done! | 491 // Done! |
| 454 } | 492 } |
| 455 | 493 |
| 456 } // namespace | 494 } // namespace |
| 457 | 495 |
| 458 int main(int argc, char** argv) { | 496 int main(int argc, char** argv) { |
| 459 base::CommandLine::Init(argc, argv); | 497 if (argc < 2 || strcmp(argv[1], "start") != 0) { |
|
Lambros
2017/07/20 01:42:50
Why remove base::CommandLine() ? The code manipula
rkjnsn
2017/07/20 06:45:51
This is something that specifically came up in sec
| |
| 460 | 498 PrintUsage(); |
| 461 const base::CommandLine* command_line = | |
| 462 base::CommandLine::ForCurrentProcess(); | |
| 463 if (command_line->HasSwitch(kHelpSwitchName) || | |
| 464 command_line->HasSwitch(kQuestionSwitchName)) { | |
| 465 PrintUsage(command_line->GetProgram()); | |
| 466 std::exit(EXIT_SUCCESS); | |
| 467 } | |
| 468 | |
| 469 base::FilePath script_path = | |
| 470 command_line->GetSwitchValuePath(kScriptSwitchName); | |
| 471 std::string user = command_line->GetSwitchValueNative(kUserSwitchName); | |
| 472 bool foreground = command_line->HasSwitch(kForegroundSwitchName); | |
| 473 | |
| 474 if (script_path.empty()) { | |
| 475 std::fputs("The path to the me2me python script is required.\n", stderr); | |
| 476 std::exit(EXIT_FAILURE); | 499 std::exit(EXIT_FAILURE); |
| 477 } | 500 } |
| 478 | 501 |
| 479 if (user.empty()) { | 502 // Skip initial args |
| 480 std::fputs("The target user must be specified.\n", stderr); | 503 argc -= 2; |
| 481 std::exit(EXIT_FAILURE); | 504 argv += 2; |
| 505 | |
| 506 bool foreground = false; | |
| 507 if (argc >= 1 && strcmp(argv[0], kForegroundFlag) == 0) { | |
| 508 foreground = true; | |
| 509 argc -= 1; | |
| 510 argv += 1; | |
| 482 } | 511 } |
| 483 | 512 |
| 513 if (argc > 1) { | |
| 514 std::fputs("Too many command-line arguments.\n", stderr); | |
|
Jamie
2017/07/20 16:56:48
exit here?
rkjnsn
2017/07/20 17:39:03
Doh!
| |
| 515 } | |
| 516 | |
| 517 uid_t real_uid = getuid(); | |
| 518 std::string user; | |
| 519 | |
| 520 // Note: This logic is security sensitive. It is imperative that a non-root | |
| 521 // user is not allowed to specify an arbitrary target user. | |
| 522 if (argc == 0) { | |
| 523 if (real_uid == 0) { | |
| 524 std::fputs("Target user must be specified when run as root.\n", stderr); | |
| 525 std::exit(EXIT_FAILURE); | |
| 526 } | |
| 527 user = FindCurrentUsername(); | |
| 528 } else { | |
| 529 if (real_uid != 0) { | |
| 530 std::fputs("Target user may not be specified by non-root users.\n", | |
| 531 stderr); | |
| 532 std::exit(EXIT_FAILURE); | |
| 533 } | |
| 534 user = argv[0]; | |
| 535 } | |
| 536 | |
| 537 bool chown_stdout = false; | |
|
Lambros
2017/07/20 01:42:49
Why add a new flag?
If you prefer this new flag f
rkjnsn
2017/07/20 06:45:51
I thought this was more readable, both because one
| |
| 484 if (!foreground) { | 538 if (!foreground) { |
| 485 Daemonize(); | 539 Daemonize(); |
| 540 // Daemonizing redirects stdout to a log file, which we want to be owned by | |
| 541 // the target user. | |
| 542 chown_stdout = true; | |
| 486 } | 543 } |
| 487 | 544 |
| 488 ExecuteSession(std::move(user), script_path.value(), !foreground); | 545 ExecuteSession(std::move(user), chown_stdout, |
| 546 real_uid != 0 ? base::make_optional(real_uid) : base::nullopt); | |
| 489 } | 547 } |
| OLD | NEW |