/* * tlsdate-setter.c - privileged time setter for tlsdated * Copyright (c) 2013 The Chromium Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "config.h" #include <errno.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/prctl.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <event2/event.h> #include "src/conf.h" #include "src/dbus.h" #include "src/seccomp.h" #include "src/tlsdate.h" #include "src/util.h" /* Atomically writes the timestamp to the specified fd. */ int save_timestamp_to_fd (int fd, time_t t) { return platform->file_write(fd, &t, sizeof (t)); } void report_setter_error (siginfo_t *info) { const char *code; int killit = 0; switch (info->si_code) { case CLD_EXITED: code = "EXITED"; break; case CLD_KILLED: code = "KILLED"; break; case CLD_DUMPED: code = "DUMPED"; break; case CLD_STOPPED: code = "STOPPED"; killit = 1; break; case CLD_TRAPPED: code = "TRAPPED"; killit = 1; break; case CLD_CONTINUED: code = "CONTINUED"; killit = 1; break; default: code = "???"; killit = 1; } info ("tlsdate-setter exitting: code:%s status:%d pid:%d uid:%d", code, info->si_status, info->si_pid, info->si_uid); if (killit) kill (info->si_pid, SIGKILL); } void time_setter_coprocess (int time_fd, int notify_fd, struct state *state) { int save_fd = -1; int status; prctl (PR_SET_NAME, "tlsdated-setter"); if (state->opts.should_save_disk && !state->opts.dry_run) { const mode_t perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; /* TODO(wad) platform->file_open */ if ( (save_fd = open (state->timestamp_path, O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC, perms)) < 0 || fchmod (save_fd, perms) != 0) { /* Attempt to unlink the path on the way out. */ unlink (state->timestamp_path); status = SETTER_NO_SAVE; goto notify_and_die; } } /* XXX: Drop all privs but CAP_SYS_TIME */ #ifdef HAVE_SECCOMP_FILTER if (enable_setter_seccomp()) { status = SETTER_NO_SBOX; goto notify_and_die; } #endif while (1) { struct timeval tv = { 0, 0 }; /* The wire protocol is a time_t, but the caller should * always be the unprivileged tlsdated process which spawned this * helper. * There are two special messages: * (time_t) 0: requests a clean shutdown * (time_t) < 0: indicates not to write to disk * On Linux, time_t is a signed long. Expanding the protocol * is easy, but writing one long only is ideal. */ ssize_t bytes = read (time_fd, &tv.tv_sec, sizeof (tv.tv_sec)); int save = 1; if (bytes == -1) { if (errno == EINTR) continue; status = SETTER_READ_ERR; goto notify_and_die; } if (bytes == 0) { /* End of pipe */ status = SETTER_READ_ERR; goto notify_and_die; } if (bytes != sizeof (tv.tv_sec)) continue; if (tv.tv_sec < 0) { /* Don't write to disk */ tv.tv_sec = -tv.tv_sec; save = 0; } if (tv.tv_sec == 0) { status = SETTER_EXIT; goto notify_and_die; } if (is_sane_time (tv.tv_sec)) { /* It would be nice if time was only allowed to move forward, but * if a single time source is wrong, then it could make it impossible * to recover from once the time is written to disk. */ status = SETTER_BAD_TIME; if (!state->opts.dry_run) { if (settimeofday (&tv, NULL) < 0) { status = SETTER_SET_ERR; goto notify_and_die; } if (state->opts.should_sync_hwclock && platform->rtc_write(&state->hwclock, &tv)) { status = SETTER_NO_RTC; goto notify_and_die; } if (save && save_fd != -1 && save_timestamp_to_fd (save_fd, tv.tv_sec)) { status = SETTER_NO_SAVE; goto notify_and_die; } } status = SETTER_TIME_SET; } /* TODO(wad) platform->file_write */ IGNORE_EINTR (write (notify_fd, &status, sizeof(status))); } notify_and_die: IGNORE_EINTR (write (notify_fd, &status, sizeof(status))); close (notify_fd); close (save_fd); _exit (status); }