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

Side by Side Diff: remoting/host/linux/remoting_user_session.cc

Issue 2939263003: Add support for CRD user-session to operate setuid (Closed)
Patch Set: Refactor to reduce code run as root 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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 }
OLDNEW
« 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