blob: fccb987e09b82147fc54c63a072dc022e02b61d6 [file] [log] [blame]
/*
* 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 <linux/rtc.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.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)
{
struct iovec iov[1];
ssize_t ret;
iov[0].iov_base = &t;
iov[0].iov_len = sizeof (t);
ret = IGNORE_EINTR (pwritev (fd, iov, 1, 0));
if (ret != sizeof (t))
return 1;
return 0;
}
/*
* Set the hardware clock referred to by fd (which should be a descriptor to
* some device that implements the interface documented in rtc(4)) to the system
* time. See hwclock(8) for details of why this is important. If we fail, we
* just return - there's nothing the caller can really do about a failure of
* this function except try later.
*/
int
sync_hwclock (int fd, time_t sec)
{
struct tm tm;
struct rtc_time rtctm;
gmtime_r (&sec, &tm);
/* these structs are identical, but separately defined */
rtctm.tm_sec = tm.tm_sec;
rtctm.tm_min = tm.tm_min;
rtctm.tm_hour = tm.tm_hour;
rtctm.tm_mday = tm.tm_mday;
rtctm.tm_mon = tm.tm_mon;
rtctm.tm_year = tm.tm_year;
rtctm.tm_wday = tm.tm_wday;
rtctm.tm_yday = tm.tm_yday;
rtctm.tm_isdst = tm.tm_isdst;
return ioctl (fd, RTC_SET_TIME, &rtctm);
}
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)
{
if ( (save_fd = open (state->timestamp_path,
O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 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 &&
sync_hwclock (state->hwclock_fd, tv.tv_sec))
{
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;
}
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);
}