remoting_user_session.cc 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  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
  3. // found in the LICENSE file.
  4. //
  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
  7. // the specified user before running the me2me session script.
  8. // Usage: user-session start [--foreground] [--user user] [-- SCRIPT_ARGS...]
  9. //
  10. // Options:
  11. // --foreground - Don't daemonize.
  12. // --user - Create a session for the specified user. Required when
  13. // running as root, not allowed when running as a normal user.
  14. // SCRIPT_ARGS - Arguments following -- are passed to the script verbatim.
  15. #include <fcntl.h>
  16. #include <grp.h>
  17. #include <limits.h>
  18. #include <pwd.h>
  19. #include <security/pam_appl.h>
  20. #include <signal.h>
  21. #include <sys/stat.h>
  22. #include <sys/types.h>
  23. #include <sys/wait.h>
  24. #include <unistd.h>
  25. #include <cerrno>
  26. #include <cstdio>
  27. #include <cstdlib>
  28. #include <cstring>
  29. #include <ctime>
  30. #include <map>
  31. #include <memory>
  32. #include <string>
  33. #include <tuple>
  34. #include <utility>
  35. #include <vector>
  36. #include "base/environment.h"
  37. #include "base/files/file_path.h"
  38. #include "base/files/file_util.h"
  39. #include "base/logging.h"
  40. #include "base/process/launch.h"
  41. #include "base/strings/strcat.h"
  42. #include "base/strings/string_piece.h"
  43. #include "base/strings/string_util.h"
  44. #include "third_party/abseil-cpp/absl/types/optional.h"
  45. namespace {
  46. // This is the file descriptor used for the Python session script to pass us
  47. // messages during startup. It must be kept in sync with USER_SESSION_MESSAGE_FD
  48. // in linux_me2me_host.py. It should be high enough that login scripts are
  49. // unlikely to interfere with it, but is otherwise arbitrary.
  50. const int kMessageFd = 202;
  51. // This is the exit code the Python session script will use to signal that the
  52. // user-session wrapper should restart instead of exiting. It must be kept in
  53. // sync with RELAUNCH_EXIT_CODE in linux_me2me_host.py
  54. const int kRelaunchExitCode = 41;
  55. const char kPamName[] = "chrome-remote-desktop";
  56. const char kScriptName[] = "chrome-remote-desktop";
  57. const char kStartCommand[] = "start";
  58. const char kForegroundFlag[] = "--foreground";
  59. const char kUserFlag[] = "--user";
  60. const char kExeSymlink[] = "/proc/self/exe";
  61. // This template will be formatted by strftime and then used by mkstemp
  62. const char kLogFileTemplate[] =
  63. "/tmp/chrome_remote_desktop_%Y%m%d_%H%M%S_XXXXXX";
  64. // The filename for the latest log symlink.
  65. constexpr char kLatestLogSymlink[] = "/tmp/chrome_remote_desktop.latest";
  66. const char kUsageMessage[] =
  67. "This program is not intended to be run by end users. To configure Chrome\n"
  68. "Remote Desktop, please install the app from the Chrome Web Store:\n"
  69. "https://chrome.google.com/remotedesktop\n";
  70. // A list of variable to pass through to the child environment. Should be kept
  71. // in sync with remoting_user_session_wrapper.sh for testing.
  72. const char* const kPassthroughVariables[] = {
  73. "GOOGLE_CLIENT_ID_REMOTING", "GOOGLE_CLIENT_ID_REMOTING_HOST",
  74. "GOOGLE_CLIENT_SECRET_REMOTING", "GOOGLE_CLIENT_SECRET_REMOTING_HOST",
  75. "CHROME_REMOTE_DESKTOP_HOST_EXTRA_PARAMS"};
  76. // Holds the null-terminated path to this executable. This is obtained at
  77. // startup, since it may be harder to obtain later. (E.g., Linux will append
  78. // " (deleted)" if the file has been replaced by an update.)
  79. char gExecutablePath[PATH_MAX] = {};
  80. void PrintUsage() {
  81. std::fputs(kUsageMessage, stderr);
  82. }
  83. // Shell-escapes a single argument in a way that is compatible with various
  84. // different shells. Returns nullopt when argument contains a newline, which
  85. // can't be represented in a cross-shell fashion.
  86. absl::optional<std::string> ShellEscapeArgument(
  87. const base::StringPiece argument) {
  88. std::string result;
  89. for (char character : argument) {
  90. // csh in particular doesn't provide a good way to handle this
  91. if (character == '\n') {
  92. return absl::nullopt;
  93. }
  94. // Some shells ascribe special meaning to some escape sequences such as \t,
  95. // so don't escape any alphanumerics. (Also cuts down on verbosity.) This is
  96. // similar to the approach sudo takes.
  97. if (!((character >= '0' && character <= '9') ||
  98. (character >= 'A' && character <= 'Z') ||
  99. (character >= 'a' && character <= 'z') ||
  100. (character == '-' || character == '_'))) {
  101. result.push_back('\\');
  102. }
  103. result.push_back(character);
  104. }
  105. return result;
  106. }
  107. // PAM conversation function. Since the wrapper runs in a non-interactive
  108. // context, log any messages, but return an error if asked to provide user
  109. // input.
  110. extern "C" int Converse(int num_messages,
  111. const struct pam_message** messages,
  112. struct pam_response** responses,
  113. void* context) {
  114. bool failed = false;
  115. for (int i = 0; i < num_messages; ++i) {
  116. // This is correct for the PAM included with Linux, OS X, and BSD. However,
  117. // apparently Solaris and HP/UX require instead `&(*msg)[i]`. That is, they
  118. // disagree as to which level of indirection contains the array.
  119. const pam_message* message = messages[i];
  120. switch (message->msg_style) {
  121. case PAM_PROMPT_ECHO_OFF:
  122. case PAM_PROMPT_ECHO_ON:
  123. LOG(WARNING) << "PAM requested user input (unsupported): "
  124. << (message->msg ? message->msg : "");
  125. failed = true;
  126. break;
  127. case PAM_TEXT_INFO:
  128. LOG(INFO) << "[PAM] " << (message->msg ? message->msg : "");
  129. break;
  130. case PAM_ERROR_MSG:
  131. // Error messages from PAM are not necessarily fatal to the operation,
  132. // as the module may be optional.
  133. LOG(WARNING) << "[PAM] " << (message->msg ? message->msg : "");
  134. break;
  135. default:
  136. LOG(WARNING) << "Encountered unknown PAM message style";
  137. failed = true;
  138. break;
  139. }
  140. }
  141. if (failed)
  142. return PAM_CONV_ERR;
  143. pam_response* response_list = static_cast<pam_response*>(
  144. std::calloc(num_messages, sizeof(*response_list)));
  145. if (response_list == nullptr)
  146. return PAM_BUF_ERR;
  147. *responses = response_list;
  148. return PAM_SUCCESS;
  149. }
  150. const struct pam_conv kPamConversation = {Converse, nullptr};
  151. // Wrapper class for working with PAM and cleaning up in an RAII fashion
  152. class PamHandle {
  153. public:
  154. // Attempts to initialize PAM transaction. Check the result with IsInitialized
  155. // before calling any other member functions.
  156. PamHandle(const char* service_name,
  157. const char* user,
  158. const struct pam_conv* pam_conversation) {
  159. last_return_code_ =
  160. pam_start(service_name, user, pam_conversation, &pam_handle_);
  161. if (last_return_code_ != PAM_SUCCESS) {
  162. pam_handle_ = nullptr;
  163. }
  164. }
  165. PamHandle(const PamHandle&) = delete;
  166. PamHandle& operator=(const PamHandle&) = delete;
  167. // Terminates PAM transaction
  168. ~PamHandle() {
  169. if (pam_handle_ != nullptr) {
  170. pam_end(pam_handle_, last_return_code_);
  171. }
  172. }
  173. // Checks whether the PAM transaction was successfully initialized. Only call
  174. // other member functions if this returns true.
  175. bool IsInitialized() const { return pam_handle_ != nullptr; }
  176. // Performs account validation
  177. int AccountManagement(int flags) {
  178. return last_return_code_ = pam_acct_mgmt(pam_handle_, flags);
  179. }
  180. // Establishes or deletes PAM user credentials
  181. int SetCredentials(int flags) {
  182. return last_return_code_ = pam_setcred(pam_handle_, flags);
  183. }
  184. // Starts user session
  185. int OpenSession(int flags) {
  186. return last_return_code_ = pam_open_session(pam_handle_, flags);
  187. }
  188. // Ends user session
  189. int CloseSession(int flags) {
  190. return last_return_code_ = pam_close_session(pam_handle_, flags);
  191. }
  192. int SetItem(int item_type, const char* value) {
  193. return last_return_code_ = pam_set_item(pam_handle_, item_type, value);
  194. }
  195. // Returns the current username according to PAM. It is possible for PAM
  196. // modules to change this from the initial value passed to the constructor.
  197. absl::optional<std::string> GetUser() {
  198. const char* user;
  199. last_return_code_ = pam_get_item(pam_handle_, PAM_USER,
  200. reinterpret_cast<const void**>(&user));
  201. if (last_return_code_ != PAM_SUCCESS || user == nullptr)
  202. return absl::nullopt;
  203. return std::string(user);
  204. }
  205. // Sets a PAM environment variable.
  206. int PutEnv(base::StringPiece name, base::StringPiece value) {
  207. std::string name_value = base::StrCat({name, "=", value});
  208. return last_return_code_ = pam_putenv(pam_handle_, name_value.c_str());
  209. }
  210. // Obtains the list of environment variables provided by PAM modules.
  211. absl::optional<base::EnvironmentMap> GetEnvironment() {
  212. char** environment = pam_getenvlist(pam_handle_);
  213. if (environment == nullptr)
  214. return absl::nullopt;
  215. base::EnvironmentMap environment_map;
  216. for (char** variable = environment; *variable != nullptr; ++variable) {
  217. char* delimiter = std::strchr(*variable, '=');
  218. if (delimiter != nullptr) {
  219. environment_map[std::string(*variable, delimiter)] =
  220. std::string(delimiter + 1);
  221. }
  222. std::free(*variable);
  223. }
  224. std::free(environment);
  225. return environment_map;
  226. }
  227. // Returns a description of the given return code
  228. const char* ErrorString(int return_code) {
  229. return pam_strerror(pam_handle_, return_code);
  230. }
  231. // Logs a fatal error if return_code isn't PAM_SUCCESS
  232. void CheckReturnCode(int return_code, base::StringPiece what) {
  233. if (return_code != PAM_SUCCESS) {
  234. LOG(FATAL) << "[PAM] " << what << ": " << ErrorString(return_code);
  235. }
  236. }
  237. private:
  238. pam_handle_t* pam_handle_ = nullptr;
  239. int last_return_code_ = PAM_SUCCESS;
  240. };
  241. // Initializes the gExecutablePath global to the location of the running
  242. // executable. Should be called at program start.
  243. void DetermineExecutablePath() {
  244. ssize_t path_size =
  245. readlink(kExeSymlink, gExecutablePath, std::size(gExecutablePath));
  246. PCHECK(path_size >= 0) << "Failed to determine executable location";
  247. CHECK(path_size < PATH_MAX) << "Executable path too long";
  248. gExecutablePath[path_size] = '\0';
  249. CHECK(gExecutablePath[0] == '/') << "Executable path not absolute";
  250. }
  251. // Returns the expected location of the session script based on the path to
  252. // this executable.
  253. std::string FindScriptPath() {
  254. return base::FilePath(gExecutablePath).DirName().Append(kScriptName).value();
  255. }
  256. // Execs the me2me script.
  257. // This function is called after forking and dropping privileges. It never
  258. // returns.
  259. [[noreturn]] void ExecMe2MeScript(base::EnvironmentMap environment,
  260. const struct passwd* pwinfo,
  261. const std::vector<std::string>& script_args) {
  262. std::string login_shell = pwinfo->pw_shell;
  263. if (login_shell.empty()) {
  264. // According to "man 5 passwd", if the shell field is empty, it defaults to
  265. // "/bin/sh".
  266. login_shell = "/bin/sh";
  267. }
  268. // By convention, a login shell is signified by preceding the shell name in
  269. // argv[0] with a '-'.
  270. std::string shell_name = '-' + base::FilePath(login_shell).BaseName().value();
  271. absl::optional<std::string> escaped_script_path =
  272. ShellEscapeArgument(FindScriptPath());
  273. CHECK(escaped_script_path) << "Could not escape script path";
  274. std::string shell_arg = *escaped_script_path + " --start --child-process";
  275. for (const std::string& arg : script_args) {
  276. absl::optional<std::string> escaped_arg = ShellEscapeArgument(arg);
  277. CHECK(escaped_arg) << "Could not escape script argument";
  278. shell_arg += " ";
  279. shell_arg += *escaped_arg;
  280. }
  281. environment["USER"] = pwinfo->pw_name;
  282. environment["LOGNAME"] = pwinfo->pw_name;
  283. environment["HOME"] = pwinfo->pw_dir;
  284. environment["SHELL"] = login_shell;
  285. if (!environment.count("PATH")) {
  286. environment["PATH"] = "/bin:/usr/bin";
  287. }
  288. environment["CHROME_REMOTE_DESKTOP_SESSION"] = "1";
  289. std::vector<std::string> env_strings;
  290. for (const auto& env_var : environment) {
  291. env_strings.emplace_back(env_var.first + "=" + env_var.second);
  292. }
  293. std::vector<const char*> arg_ptrs = {shell_name.c_str(), "-c",
  294. shell_arg.c_str(), nullptr};
  295. std::vector<const char*> env_ptrs;
  296. env_ptrs.reserve(env_strings.size() + 1);
  297. for (const auto& env_string : env_strings) {
  298. env_ptrs.push_back(env_string.c_str());
  299. }
  300. env_ptrs.push_back(nullptr);
  301. execve(login_shell.c_str(), const_cast<char* const*>(arg_ptrs.data()),
  302. const_cast<char* const*>(env_ptrs.data()));
  303. PLOG(FATAL) << "Failed to exec login shell " << login_shell;
  304. // The FATAL log should have terminated the program already, but this makes
  305. // the compiler happy.
  306. std::exit(EXIT_FAILURE);
  307. }
  308. // Either |user| must be set when running as root, xor the real user ID must be
  309. // properly set when running as a user.
  310. void Relaunch(const absl::optional<std::string>& user,
  311. const std::vector<std::string>& script_args) {
  312. CHECK(user.has_value() == (getuid() == 0));
  313. // Pass --foreground to continue using the same log file.
  314. std::vector<const char*> arg_ptrs = {gExecutablePath, kStartCommand,
  315. kForegroundFlag};
  316. if (user) {
  317. arg_ptrs.push_back(kUserFlag);
  318. arg_ptrs.push_back(user->c_str());
  319. }
  320. arg_ptrs.push_back("--");
  321. for (const std::string& arg : script_args) {
  322. arg_ptrs.push_back(arg.c_str());
  323. }
  324. arg_ptrs.push_back(nullptr);
  325. execv(gExecutablePath, const_cast<char* const*>(arg_ptrs.data()));
  326. PCHECK(false) << "Failed to exec self";
  327. }
  328. // Runs the me2me script in a PAM session. Exits the program on failure.
  329. // If chown_log is true, the owner and group of the file associated with stdout
  330. // will be changed to the target user. If match_uid is specified, this function
  331. // will fail if the final user id does not match the one provided. If
  332. // script_args is not empty, the contained arguments will be passed on to the
  333. // me2me script.
  334. //
  335. // Returns: whether the session should be relaunched.
  336. bool ExecuteSession(std::string user,
  337. bool chown_log,
  338. absl::optional<uid_t> match_uid,
  339. const std::vector<std::string>& script_args) {
  340. PamHandle pam_handle(kPamName, user.c_str(), &kPamConversation);
  341. CHECK(pam_handle.IsInitialized()) << "Failed to initialize PAM";
  342. // Since we're running setuid root, we don't want to risk any user-set
  343. // environment variables changing the behavior of PAM modules, so copy any
  344. // variables we explicitly want to preserve into the PAM session and then
  345. // clear the environment.
  346. for (const char* variable : kPassthroughVariables) {
  347. char* value = std::getenv(variable);
  348. if (value != nullptr) {
  349. pam_handle.CheckReturnCode(pam_handle.PutEnv(variable, value),
  350. "Environment passthrough");
  351. }
  352. }
  353. clearenv();
  354. // Set various session attributes.
  355. pam_handle.CheckReturnCode(pam_handle.PutEnv("XDG_SESSION_CLASS", "user"),
  356. "Set session class");
  357. pam_handle.CheckReturnCode(pam_handle.PutEnv("XDG_SESSION_TYPE", "x11"),
  358. "Set session type");
  359. // Ideally, the TTY should be set to the X display for x11 sessions, but we
  360. // don't yet know what display we'll be using. Apparently some PAM modules
  361. // (the pam_systemd documentation explicitly calls out pam_time and
  362. // pam_access) require PAM_TTY to be set, so we set it to a dummy value. There
  363. // is some precedence for this, as SSH and cron set PAM_TTY to "ssh" and
  364. // "cron" (respectively) for similar reasons.
  365. // TODO(rkjnsn): This will prevent any PAM modules from, e.g., setting
  366. // session-related X properties. It would be more correct to implement a two-
  367. // phase session setup: first creating a "background/unspecified" session to
  368. // run the me2me script and the X server, and then launching a "user/x11"
  369. // session with PAM_TTY and PAM_XDISPLAY properly set to run the session
  370. // chooser or the user's session script. This would also allow the inner
  371. // session to be completely cleaned-up when the user selects "Logout" from
  372. // within their chromoting session.
  373. pam_handle.CheckReturnCode(
  374. pam_handle.SetItem(PAM_TTY, "chrome-remote-desktop"), "Set PAM_TTY");
  375. // Make sure the account is valid and enabled.
  376. pam_handle.CheckReturnCode(pam_handle.AccountManagement(0), "Account check");
  377. // PAM may remap the user at any stage.
  378. user = pam_handle.GetUser().value_or(std::move(user));
  379. // setcred explicitly does not handle user id or group membership, and
  380. // specifies that they should be established before calling setcred. Only the
  381. // real user id is set here, as we still require root privileges. PAM modules
  382. // may use getpwnam, so pwinfo can only be assumed valid until the next PAM
  383. // call.
  384. errno = 0;
  385. struct passwd* pwinfo = getpwnam(user.c_str());
  386. PCHECK(pwinfo != nullptr) << "getpwnam failed";
  387. PCHECK(setreuid(pwinfo->pw_uid, -1) == 0) << "setreuid failed";
  388. PCHECK(setgid(pwinfo->pw_gid) == 0) << "setgid failed";
  389. PCHECK(initgroups(pwinfo->pw_name, pwinfo->pw_gid) == 0)
  390. << "initgroups failed";
  391. // The documentation states that setcred should be called before open_session,
  392. // as done here, but it may be worth noting that `login` calls open_session
  393. // first.
  394. pam_handle.CheckReturnCode(pam_handle.SetCredentials(PAM_ESTABLISH_CRED),
  395. "Set credentials");
  396. pam_handle.CheckReturnCode(pam_handle.OpenSession(0), "Open session");
  397. // The above may have remapped the user.
  398. user = pam_handle.GetUser().value_or(std::move(user));
  399. // Fetch pwinfo again, as it may have been invalidated or the user name might
  400. // have been remapped.
  401. pwinfo = getpwnam(user.c_str());
  402. PCHECK(pwinfo != nullptr) << "getpwnam failed";
  403. if (match_uid && pwinfo->pw_uid != *match_uid) {
  404. LOG(FATAL) << "PAM remapped username to one with a different user ID.";
  405. }
  406. if (chown_log) {
  407. int result = fchown(STDOUT_FILENO, pwinfo->pw_uid, pwinfo->pw_gid);
  408. PLOG_IF(WARNING, result != 0) << "Failed to change log file owner";
  409. result = lchown(kLatestLogSymlink, pwinfo->pw_uid, pwinfo->pw_gid);
  410. PLOG_IF(WARNING, result != 0)
  411. << "Failed to change latest log symlink owner";
  412. }
  413. pid_t child_pid = fork();
  414. PCHECK(child_pid >= 0) << "fork failed";
  415. if (child_pid == 0) {
  416. PCHECK(setuid(pwinfo->pw_uid) == 0) << "setuid failed";
  417. PCHECK(chdir(pwinfo->pw_dir) == 0) << "chdir to $HOME failed";
  418. absl::optional<base::EnvironmentMap> pam_environment =
  419. pam_handle.GetEnvironment();
  420. CHECK(pam_environment) << "Failed to get environment from PAM";
  421. // Never returns.
  422. ExecMe2MeScript(std::move(*pam_environment), pwinfo, script_args);
  423. } else {
  424. // Close pipe write fd if it is open.
  425. close(kMessageFd);
  426. // waitpid will return if the child is ptraced, so loop until the process
  427. // actually exits.
  428. int status;
  429. do {
  430. pid_t wait_result = waitpid(child_pid, &status, 0);
  431. // Die if wait fails so we don't close the PAM session while the child is
  432. // still running.
  433. PCHECK(wait_result >= 0) << "wait failed";
  434. } while (!WIFEXITED(status) && !WIFSIGNALED(status));
  435. bool relaunch = false;
  436. if (WIFEXITED(status)) {
  437. if (WEXITSTATUS(status) == EXIT_SUCCESS) {
  438. LOG(INFO) << "Child exited successfully";
  439. } else if (WEXITSTATUS(status) == kRelaunchExitCode) {
  440. LOG(INFO) << "Restarting session";
  441. relaunch = true;
  442. } else {
  443. LOG(WARNING) << "Child exited with status " << WEXITSTATUS(status);
  444. }
  445. } else if (WIFSIGNALED(status)) {
  446. LOG(WARNING) << "Child terminated by signal " << WTERMSIG(status);
  447. }
  448. // Best effort PAM cleanup
  449. if (pam_handle.CloseSession(0) != PAM_SUCCESS) {
  450. LOG(WARNING) << "Failed to close PAM session";
  451. }
  452. std::ignore = pam_handle.SetCredentials(PAM_DELETE_CRED);
  453. return relaunch;
  454. }
  455. }
  456. struct LogFile {
  457. int fd;
  458. std::string path;
  459. };
  460. // Opens a temp file for logging. Exits the program on failure.
  461. // Returns open file descriptor and path to log file.
  462. LogFile OpenLogFile() {
  463. char logfile[265];
  464. std::time_t time = std::time(nullptr);
  465. CHECK_NE(time, (std::time_t)(-1));
  466. // Safe because we're single threaded
  467. std::tm* localtime = std::localtime(&time);
  468. CHECK_NE(std::strftime(logfile, sizeof(logfile), kLogFileTemplate, localtime),
  469. static_cast<std::size_t>(0))
  470. << "Failed to format log file name";
  471. mode_t mode = umask(0177);
  472. int fd = mkstemp(logfile);
  473. PCHECK(fd != -1) << "Failed to open log file";
  474. // Creates a symlink to make the logs easier to find.
  475. int symlink_ret = symlink(logfile, kLatestLogSymlink);
  476. if (symlink_ret != 0 && errno == EEXIST) {
  477. unlink(kLatestLogSymlink);
  478. symlink_ret = symlink(logfile, kLatestLogSymlink);
  479. }
  480. PLOG_IF(ERROR, symlink_ret != 0)
  481. << "Failed to create log symlink to " << logfile;
  482. umask(mode);
  483. return {fd, logfile};
  484. }
  485. // Find the username for the current user. If either USER or LOGNAME is set to
  486. // a user matching our real user id, we return that. Otherwise, we use getpwuid
  487. // to attempt a reverse lookup. Note: It's possible for multiple usernames to
  488. // share the same user id (e.g., to allow a user to have logins with different
  489. // home directories or group membership, but be considered the same user as far
  490. // as file permissions are concerned). Consulting USER/LOGNAME allows us to pick
  491. // the correct entry in these circumstances.
  492. std::string FindCurrentUsername() {
  493. uid_t real_uid = getuid();
  494. struct passwd* pwinfo;
  495. for (const char* var : {"USER", "LOGNAME"}) {
  496. const char* value = getenv(var);
  497. if (value) {
  498. pwinfo = getpwnam(value);
  499. // USER and LOGNAME can be overridden, so make sure the value is valid
  500. // and matches the UID of the invoking user.
  501. if (pwinfo && pwinfo->pw_uid == real_uid) {
  502. return pwinfo->pw_name;
  503. }
  504. }
  505. }
  506. errno = 0;
  507. pwinfo = getpwuid(real_uid);
  508. PCHECK(pwinfo) << "getpwuid failed";
  509. return pwinfo->pw_name;
  510. }
  511. // Handle SIGINT and SIGTERM by printing a message and reraising the signal.
  512. // This handler expects to be registered with the SA_RESETHAND and SA_NODEFER
  513. // options to sigaction. (Don't register using signal.)
  514. void HandleInterrupt(int signal) {
  515. static const char kInterruptedMessage[] =
  516. "Interrupted. The daemon is still running in the background.\n";
  517. // Use write since fputs isn't async-signal-handler safe.
  518. std::ignore = write(STDERR_FILENO, kInterruptedMessage,
  519. std::size(kInterruptedMessage) - 1);
  520. raise(signal);
  521. }
  522. // Handle SIGALRM timeout
  523. void HandleAlarm(int) {
  524. static const char kTimeoutMessage[] =
  525. "Timeout waiting for session to start. It may have crashed, or may still "
  526. "be running in the background.\n";
  527. // Use write since fputs isn't async-signal-handler safe.
  528. std::ignore =
  529. write(STDERR_FILENO, kTimeoutMessage, std::size(kTimeoutMessage) - 1);
  530. // A slow system or directory replication delay may cause the host to take
  531. // longer than expected to start. Since it may still succeed, optimistically
  532. // return success to prevent the host from being automatically unregistered.
  533. std::_Exit(EXIT_SUCCESS);
  534. }
  535. // Relay messages from the host session and then exit.
  536. void WaitForMessagesAndExit(int read_fd, const std::string& log_name) {
  537. // Use initializer-list syntax to avoid trailing null
  538. static const base::StringPiece kMessagePrefix = "MSG:";
  539. static const base::StringPiece kReady = "READY\n";
  540. struct sigaction action = {};
  541. sigemptyset(&action.sa_mask);
  542. action.sa_flags = SA_RESETHAND | SA_NODEFER;
  543. // If Ctrl-C is pressed or TERM is received, inform the user that the daemon
  544. // is still running before exiting.
  545. action.sa_handler = HandleInterrupt;
  546. sigaction(SIGINT, &action, nullptr);
  547. sigaction(SIGTERM, &action, nullptr);
  548. // Install a fallback timeout to end the parent process, in case the daemon
  549. // never responds (e.g. host crash-looping, daemon killed).
  550. //
  551. // The value of 120s is chosen to match the heartbeat retry timeout in
  552. // hearbeat_sender.cc.
  553. action.sa_handler = HandleAlarm;
  554. sigaction(SIGALRM, &action, nullptr);
  555. alarm(120);
  556. std::FILE* stream = fdopen(read_fd, "r");
  557. char* buffer = nullptr;
  558. std::size_t buffer_size = 0;
  559. ssize_t line_size;
  560. bool message_received = false;
  561. bool host_ready = false;
  562. while ((line_size = getline(&buffer, &buffer_size, stream)) >= 0) {
  563. message_received = true;
  564. base::StringPiece line(buffer, line_size);
  565. if (base::StartsWith(line, kMessagePrefix, base::CompareCase::SENSITIVE)) {
  566. line.remove_prefix(kMessagePrefix.size());
  567. std::fwrite(line.data(), sizeof(char), line.size(), stderr);
  568. } else if (line == kReady) {
  569. host_ready = true;
  570. } else {
  571. std::fputs("Unrecognized command: ", stderr);
  572. std::fwrite(line.data(), sizeof(char), line.size(), stderr);
  573. }
  574. }
  575. // If we're not at EOF, it means a read error occured and we don't know if the
  576. // host is still running or not. Similarly, if we received an EOF before any
  577. // messages were received, it probably means the user's log-in shell closed
  578. // the pipe before execing the python script, so again we don't know the state
  579. // of the host. This latter behavior has only been observed in csh and tcsh.
  580. // All other shells tested allowed the python script to inherit the pipe file
  581. // descriptor without trouble.
  582. if (!std::feof(stream) || !message_received) {
  583. LOG(WARNING) << "Failed to read from message pipe. Please check log to "
  584. "determine host status.\n";
  585. // Assume host started so native messaging host allows flow to complete.
  586. host_ready = true;
  587. }
  588. std::fprintf(stderr, "Log file: %s\n", log_name.c_str());
  589. std::exit(host_ready ? EXIT_SUCCESS : EXIT_FAILURE);
  590. }
  591. // Daemonizes the process. Output is redirected to a log file. Exits the program
  592. // on failure. Only returns in the child process.
  593. //
  594. // When executed by root (almost certainly via the init script), or if a pipe
  595. // cannot be created, the parent will immediately exit. When executed by a
  596. // user, the parent process will drop privileges and wait for the host to
  597. // start, relaying any start-up messages to stdout.
  598. //
  599. // TODO(lambroslambrou): Having stdout/stderr redirected to a log file is not
  600. // ideal - it could create a filesystem DoS if the daemon or a child process
  601. // were to write excessive amounts to stdout/stderr. Ideally, stdout/stderr
  602. // should be redirected to a pipe or socket, and a process at the other end
  603. // should consume the data and write it to a logging facility which can do
  604. // data-capping or log-rotation. The 'logger' command-line utility could be
  605. // used for this, but it might cause too much syslog spam.
  606. void Daemonize() {
  607. // Open file descriptors before forking so errors can be reported.
  608. LogFile log_file = OpenLogFile();
  609. int devnull_fd = open("/dev/null", O_RDONLY);
  610. PCHECK(devnull_fd != -1) << "Failed to open /dev/null";
  611. uid_t real_uid = getuid();
  612. // Set up message pipe
  613. bool pipe_created = false;
  614. int read_fd;
  615. if (real_uid != 0) {
  616. int pipe_fd[2];
  617. int pipe_result = ::pipe(pipe_fd);
  618. if (pipe_result != 0 || dup2(pipe_fd[1], kMessageFd) != kMessageFd) {
  619. PLOG(WARNING) << "Failed to create message pipe. Please check log to "
  620. "determine host status.\n";
  621. } else {
  622. pipe_created = true;
  623. read_fd = pipe_fd[0];
  624. close(pipe_fd[1]);
  625. }
  626. }
  627. // Allow parent to exit, and ensure we're not a session leader so setsid can
  628. // succeed
  629. pid_t pid = fork();
  630. PCHECK(pid != -1) << "fork failed";
  631. if (pid != 0) {
  632. if (!pipe_created) {
  633. std::exit(EXIT_SUCCESS);
  634. } else {
  635. PCHECK(setuid(real_uid) == 0) << "setuid failed";
  636. close(kMessageFd);
  637. WaitForMessagesAndExit(read_fd, log_file.path);
  638. CHECK(false);
  639. }
  640. }
  641. // Start a new process group and session with no controlling terminal.
  642. PCHECK(setsid() != -1) << "setsid failed";
  643. // Fork again so we're no longer a session leader and can't get a controlling
  644. // terminal.
  645. pid = fork();
  646. PCHECK(pid != -1) << "fork failed";
  647. if (pid != 0) {
  648. std::exit(EXIT_SUCCESS);
  649. }
  650. LOG(INFO) << "Daemon process started in the background, logging to '"
  651. << log_file.path << "'";
  652. // We don't want to change to the target user's home directory until we've
  653. // dropped privileges, so change to / to make sure we're not keeping any other
  654. // directory in use.
  655. PCHECK(chdir("/") == 0) << "chdir / failed";
  656. PCHECK(dup2(devnull_fd, STDIN_FILENO) != -1) << "dup2 failed";
  657. PCHECK(dup2(log_file.fd, STDOUT_FILENO) != -1) << "dup2 failed";
  658. PCHECK(dup2(log_file.fd, STDERR_FILENO) != -1) << "dup2 failed";
  659. // Close all file descriptors except stdio and kMessageFd, including any we
  660. // may have inherited.
  661. if (pipe_created) {
  662. base::CloseSuperfluousFds(
  663. {base::InjectionArc(kMessageFd, kMessageFd, false)});
  664. } else {
  665. base::CloseSuperfluousFds(base::InjectiveMultimap());
  666. }
  667. }
  668. } // namespace
  669. int main(int argc, char** argv) {
  670. // Initialize gExecutablePath
  671. DetermineExecutablePath();
  672. // This binary requires elevated privileges.
  673. if (geteuid() != 0) {
  674. std::fprintf(stderr,
  675. "%s not installed setuid root. Host must be started by "
  676. "administrator.\n",
  677. gExecutablePath);
  678. std::exit(EXIT_FAILURE);
  679. }
  680. if (argc < 2 || std::strcmp(argv[1], kStartCommand) != 0) {
  681. PrintUsage();
  682. std::exit(EXIT_FAILURE);
  683. }
  684. // Skip initial args
  685. argc -= 2;
  686. argv += 2;
  687. bool foreground = false;
  688. absl::optional<std::string> user;
  689. std::vector<std::string> script_args;
  690. while (argc > 0) {
  691. if (std::strcmp(argv[0], kForegroundFlag) == 0) {
  692. foreground = true;
  693. argc -= 1;
  694. argv += 1;
  695. } else if (std::strcmp(argv[0], kUserFlag) == 0 && argc >= 2) {
  696. user = std::string(argv[1]);
  697. argc -= 2;
  698. argv += 2;
  699. } else if (std::strcmp(argv[0], "--") == 0) {
  700. argc -= 1;
  701. argv += 1;
  702. // Remaining args get forwarded to python script.
  703. while (argc > 0) {
  704. script_args.emplace_back(argv[0]);
  705. argc -= 1;
  706. argv += 1;
  707. }
  708. } else {
  709. PrintUsage();
  710. std::exit(EXIT_FAILURE);
  711. }
  712. }
  713. uid_t real_uid = getuid();
  714. // Note: This logic is security sensitive. It is imperative that a non-root
  715. // user is not allowed to specify an arbitrary target user.
  716. if (real_uid != 0) {
  717. if (user) {
  718. std::fputs("Target user may not be specified by non-root users.\n",
  719. stderr);
  720. std::exit(EXIT_FAILURE);
  721. }
  722. user = FindCurrentUsername();
  723. } else {
  724. if (!user) {
  725. std::fputs("Target user must be specified when run as root.\n", stderr);
  726. std::exit(EXIT_FAILURE);
  727. }
  728. }
  729. if (!foreground) {
  730. Daemonize();
  731. }
  732. // Daemonizing redirects stdout to a log file, which we want to be owned by
  733. // the target user.
  734. bool chown_stdout = !foreground;
  735. absl::optional<uid_t> match_uid =
  736. real_uid != 0 ? absl::make_optional(real_uid) : absl::nullopt;
  737. // Fork before opening PAM session so relaunches don't descend from the closed
  738. // PAM session.
  739. pid_t child_pid = fork();
  740. PCHECK(child_pid >= 0) << "fork failed";
  741. if (child_pid == 0) {
  742. bool relaunch = ExecuteSession(std::move(*user), chown_stdout, match_uid,
  743. std::move(script_args));
  744. std::exit(relaunch ? kRelaunchExitCode : EXIT_SUCCESS);
  745. } else {
  746. // Close pipe write fd if it is open.
  747. close(kMessageFd);
  748. // waitpid will return if the child is ptraced, so loop until the process
  749. // actually exits.
  750. int status;
  751. do {
  752. pid_t wait_result = waitpid(child_pid, &status, 0);
  753. // If we fail to wait on our child process, something has gone wrong and
  754. // there's not much we can do. Note that this means if the user later logs
  755. // out, the session won't restart.
  756. PCHECK(wait_result >= 0) << "wait failed";
  757. } while (!WIFEXITED(status) && !WIFSIGNALED(status));
  758. if (WIFEXITED(status) && WEXITSTATUS(status) == kRelaunchExitCode) {
  759. // If running as root, forward the username argument to the relaunched
  760. // process. Otherwise, it should be inferred from the user id and
  761. // environment.
  762. Relaunch(real_uid == 0 ? user : absl::nullopt, script_args);
  763. }
  764. }
  765. return EXIT_SUCCESS;
  766. }