Close /dev/urandom and drop cached getrandom() data after forking helper processes to avoid them getting the same sequence of random numbers (e.g. two NTS-KE helpers generating cookies with identical nonces). arc4random() is assumed to be able to detect forks and reseed automatically. This is not strictly necessary with the current code, which does not use the GetRandom functions before the NTS-KE helper processes are forked, but that could change in future. Also, call the reset function before exit to close /dev/urandom in order to avoid valgrind reporting the file object as "still reachable".
696 lines
15 KiB
C
696 lines
15 KiB
C
/*
|
|
chronyd/chronyc - Programs for keeping computer clocks accurate.
|
|
|
|
**********************************************************************
|
|
* Copyright (C) Bryan Christianson 2015
|
|
* Copyright (C) Miroslav Lichvar 2017
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of version 2 of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
**********************************************************************
|
|
|
|
=======================================================================
|
|
|
|
Perform privileged operations over a unix socket to a privileged fork.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "sysincl.h"
|
|
|
|
#include "conf.h"
|
|
#include "nameserv.h"
|
|
#include "logging.h"
|
|
#include "privops.h"
|
|
#include "socket.h"
|
|
#include "util.h"
|
|
|
|
#define OP_ADJUSTTIME 1024
|
|
#define OP_ADJUSTTIMEX 1025
|
|
#define OP_SETTIME 1026
|
|
#define OP_BINDSOCKET 1027
|
|
#define OP_NAME2IPADDRESS 1028
|
|
#define OP_RELOADDNS 1029
|
|
#define OP_QUIT 1099
|
|
|
|
union sockaddr_in46 {
|
|
struct sockaddr_in in4;
|
|
#ifdef FEAT_IPV6
|
|
struct sockaddr_in6 in6;
|
|
#endif
|
|
struct sockaddr u;
|
|
};
|
|
|
|
/* daemon request structs */
|
|
|
|
typedef struct {
|
|
struct timeval tv;
|
|
} ReqAdjustTime;
|
|
|
|
#ifdef PRIVOPS_ADJUSTTIMEX
|
|
typedef struct {
|
|
struct timex tmx;
|
|
} ReqAdjustTimex;
|
|
#endif
|
|
|
|
typedef struct {
|
|
struct timeval tv;
|
|
} ReqSetTime;
|
|
|
|
typedef struct {
|
|
int sock;
|
|
socklen_t sa_len;
|
|
union sockaddr_in46 sa;
|
|
} ReqBindSocket;
|
|
|
|
typedef struct {
|
|
char name[256];
|
|
} ReqName2IPAddress;
|
|
|
|
typedef struct {
|
|
int op;
|
|
union {
|
|
ReqAdjustTime adjust_time;
|
|
#ifdef PRIVOPS_ADJUSTTIMEX
|
|
ReqAdjustTimex adjust_timex;
|
|
#endif
|
|
ReqSetTime set_time;
|
|
ReqBindSocket bind_socket;
|
|
#ifdef PRIVOPS_NAME2IPADDRESS
|
|
ReqName2IPAddress name_to_ipaddress;
|
|
#endif
|
|
} data;
|
|
} PrvRequest;
|
|
|
|
/* helper response structs */
|
|
|
|
typedef struct {
|
|
struct timeval tv;
|
|
} ResAdjustTime;
|
|
|
|
#ifdef PRIVOPS_ADJUSTTIMEX
|
|
typedef struct {
|
|
struct timex tmx;
|
|
} ResAdjustTimex;
|
|
#endif
|
|
|
|
typedef struct {
|
|
IPAddr addresses[DNS_MAX_ADDRESSES];
|
|
} ResName2IPAddress;
|
|
|
|
typedef struct {
|
|
char msg[256];
|
|
} ResFatalMsg;
|
|
|
|
typedef struct {
|
|
int fatal_error;
|
|
int rc;
|
|
int res_errno;
|
|
union {
|
|
ResFatalMsg fatal_msg;
|
|
ResAdjustTime adjust_time;
|
|
#ifdef PRIVOPS_ADJUSTTIMEX
|
|
ResAdjustTimex adjust_timex;
|
|
#endif
|
|
#ifdef PRIVOPS_NAME2IPADDRESS
|
|
ResName2IPAddress name_to_ipaddress;
|
|
#endif
|
|
} data;
|
|
} PrvResponse;
|
|
|
|
static int helper_fd;
|
|
static pid_t helper_pid;
|
|
|
|
static int
|
|
have_helper(void)
|
|
{
|
|
return helper_fd >= 0;
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* HELPER - prepare fatal error for daemon */
|
|
static void
|
|
res_fatal(PrvResponse *res, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
res->fatal_error = 1;
|
|
va_start(ap, fmt);
|
|
vsnprintf(res->data.fatal_msg.msg, sizeof (res->data.fatal_msg.msg), fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* HELPER - send response to the fd */
|
|
|
|
static int
|
|
send_response(int fd, const PrvResponse *res)
|
|
{
|
|
if (SCK_Send(fd, res, sizeof (*res), 0) != sizeof (*res))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
/* receive daemon request plus optional file descriptor over a unix socket */
|
|
|
|
static int
|
|
receive_from_daemon(int fd, PrvRequest *req)
|
|
{
|
|
SCK_Message *message;
|
|
|
|
message = SCK_ReceiveMessage(fd, SCK_FLAG_MSG_DESCRIPTOR);
|
|
if (!message || message->length != sizeof (*req))
|
|
return 0;
|
|
|
|
memcpy(req, message->data, sizeof (*req));
|
|
|
|
if (req->op == OP_BINDSOCKET) {
|
|
req->data.bind_socket.sock = message->descriptor;
|
|
|
|
/* return error if valid descriptor not found */
|
|
if (req->data.bind_socket.sock < 0)
|
|
return 0;
|
|
} else if (message->descriptor >= 0) {
|
|
SCK_CloseSocket(message->descriptor);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* HELPER - perform adjtime() */
|
|
|
|
#ifdef PRIVOPS_ADJUSTTIME
|
|
static void
|
|
do_adjust_time(const ReqAdjustTime *req, PrvResponse *res)
|
|
{
|
|
res->rc = adjtime(&req->tv, &res->data.adjust_time.tv);
|
|
if (res->rc)
|
|
res->res_errno = errno;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* HELPER - perform ntp_adjtime() */
|
|
|
|
#ifdef PRIVOPS_ADJUSTTIMEX
|
|
static void
|
|
do_adjust_timex(const ReqAdjustTimex *req, PrvResponse *res)
|
|
{
|
|
res->data.adjust_timex.tmx = req->tmx;
|
|
res->rc = ntp_adjtime(&res->data.adjust_timex.tmx);
|
|
if (res->rc < 0)
|
|
res->res_errno = errno;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* HELPER - perform settimeofday() */
|
|
|
|
#ifdef PRIVOPS_SETTIME
|
|
static void
|
|
do_set_time(const ReqSetTime *req, PrvResponse *res)
|
|
{
|
|
res->rc = settimeofday(&req->tv, NULL);
|
|
if (res->rc)
|
|
res->res_errno = errno;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* HELPER - perform bind() */
|
|
|
|
#ifdef PRIVOPS_BINDSOCKET
|
|
static void
|
|
do_bind_socket(ReqBindSocket *req, PrvResponse *res)
|
|
{
|
|
IPSockAddr ip_saddr;
|
|
int sock_fd;
|
|
struct sockaddr *sa;
|
|
socklen_t sa_len;
|
|
|
|
sa = &req->sa.u;
|
|
sa_len = req->sa_len;
|
|
sock_fd = req->sock;
|
|
|
|
SCK_SockaddrToIPSockAddr(sa, sa_len, &ip_saddr);
|
|
if (ip_saddr.port != 0 && ip_saddr.port != CNF_GetNTPPort() &&
|
|
ip_saddr.port != CNF_GetAcquisitionPort() && ip_saddr.port != CNF_GetPtpPort()) {
|
|
SCK_CloseSocket(sock_fd);
|
|
res_fatal(res, "Invalid port %d", ip_saddr.port);
|
|
return;
|
|
}
|
|
|
|
res->rc = bind(sock_fd, sa, sa_len);
|
|
if (res->rc)
|
|
res->res_errno = errno;
|
|
|
|
/* sock is still open on daemon side, but we're done with it in the helper */
|
|
SCK_CloseSocket(sock_fd);
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* HELPER - perform DNS_Name2IPAddress() */
|
|
|
|
#ifdef PRIVOPS_NAME2IPADDRESS
|
|
static void
|
|
do_name_to_ipaddress(ReqName2IPAddress *req, PrvResponse *res)
|
|
{
|
|
/* make sure the string is terminated */
|
|
req->name[sizeof (req->name) - 1] = '\0';
|
|
|
|
res->rc = DNS_Name2IPAddress(req->name, res->data.name_to_ipaddress.addresses,
|
|
DNS_MAX_ADDRESSES);
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* HELPER - perform DNS_Reload() */
|
|
|
|
#ifdef PRIVOPS_RELOADDNS
|
|
static void
|
|
do_reload_dns(PrvResponse *res)
|
|
{
|
|
DNS_Reload();
|
|
res->rc = 0;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* HELPER - main loop - action requests from the daemon */
|
|
|
|
static void
|
|
helper_main(int fd)
|
|
{
|
|
PrvRequest req;
|
|
PrvResponse res;
|
|
int quit = 0;
|
|
|
|
while (!quit) {
|
|
if (!receive_from_daemon(fd, &req))
|
|
/* read error or closed input - we cannot recover - give up */
|
|
break;
|
|
|
|
memset(&res, 0, sizeof (res));
|
|
|
|
switch (req.op) {
|
|
#ifdef PRIVOPS_ADJUSTTIME
|
|
case OP_ADJUSTTIME:
|
|
do_adjust_time(&req.data.adjust_time, &res);
|
|
break;
|
|
#endif
|
|
#ifdef PRIVOPS_ADJUSTTIMEX
|
|
case OP_ADJUSTTIMEX:
|
|
do_adjust_timex(&req.data.adjust_timex, &res);
|
|
break;
|
|
#endif
|
|
#ifdef PRIVOPS_SETTIME
|
|
case OP_SETTIME:
|
|
do_set_time(&req.data.set_time, &res);
|
|
break;
|
|
#endif
|
|
#ifdef PRIVOPS_BINDSOCKET
|
|
case OP_BINDSOCKET:
|
|
do_bind_socket(&req.data.bind_socket, &res);
|
|
break;
|
|
#endif
|
|
#ifdef PRIVOPS_NAME2IPADDRESS
|
|
case OP_NAME2IPADDRESS:
|
|
do_name_to_ipaddress(&req.data.name_to_ipaddress, &res);
|
|
break;
|
|
#endif
|
|
#ifdef PRIVOPS_RELOADDNS
|
|
case OP_RELOADDNS:
|
|
do_reload_dns(&res);
|
|
break;
|
|
#endif
|
|
case OP_QUIT:
|
|
quit = 1;
|
|
continue;
|
|
|
|
default:
|
|
res_fatal(&res, "Unexpected operator %d", req.op);
|
|
break;
|
|
}
|
|
|
|
send_response(fd, &res);
|
|
}
|
|
|
|
SCK_CloseSocket(fd);
|
|
exit(0);
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - receive helper response */
|
|
|
|
static void
|
|
receive_response(PrvResponse *res)
|
|
{
|
|
int resp_len;
|
|
|
|
resp_len = SCK_Receive(helper_fd, res, sizeof (*res), 0);
|
|
if (resp_len < 0)
|
|
LOG_FATAL("Could not read from helper : %s", strerror(errno));
|
|
if (resp_len != sizeof (*res))
|
|
LOG_FATAL("Invalid helper response");
|
|
|
|
if (res->fatal_error)
|
|
LOG_FATAL("Error in helper : %s", res->data.fatal_msg.msg);
|
|
|
|
DEBUG_LOG("Received response rc=%d", res->rc);
|
|
|
|
/* if operation failed in the helper, set errno so daemon can print log message */
|
|
if (res->res_errno)
|
|
errno = res->res_errno;
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - send daemon request to the helper */
|
|
|
|
static void
|
|
send_request(PrvRequest *req)
|
|
{
|
|
SCK_Message message;
|
|
int flags;
|
|
|
|
SCK_InitMessage(&message, SCK_ADDR_UNSPEC);
|
|
|
|
message.data = req;
|
|
message.length = sizeof (*req);
|
|
flags = 0;
|
|
|
|
if (req->op == OP_BINDSOCKET) {
|
|
/* send file descriptor as a control message */
|
|
message.descriptor = req->data.bind_socket.sock;
|
|
flags |= SCK_FLAG_MSG_DESCRIPTOR;
|
|
}
|
|
|
|
if (!SCK_SendMessage(helper_fd, &message, flags)) {
|
|
/* don't try to send another request from exit() */
|
|
helper_fd = -1;
|
|
LOG_FATAL("Could not send to helper : %s", strerror(errno));
|
|
}
|
|
|
|
DEBUG_LOG("Sent request op=%d", req->op);
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - send daemon request and wait for response */
|
|
|
|
static void
|
|
submit_request(PrvRequest *req, PrvResponse *res)
|
|
{
|
|
send_request(req);
|
|
receive_response(res);
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - send the helper a request to exit and wait until it exits */
|
|
|
|
static void
|
|
stop_helper(void)
|
|
{
|
|
PrvRequest req;
|
|
int status;
|
|
|
|
if (!have_helper())
|
|
return;
|
|
|
|
memset(&req, 0, sizeof (req));
|
|
req.op = OP_QUIT;
|
|
send_request(&req);
|
|
|
|
waitpid(helper_pid, &status, 0);
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - request adjtime() */
|
|
|
|
#ifdef PRIVOPS_ADJUSTTIME
|
|
int
|
|
PRV_AdjustTime(const struct timeval *delta, struct timeval *olddelta)
|
|
{
|
|
PrvRequest req;
|
|
PrvResponse res;
|
|
|
|
if (!have_helper() || delta == NULL)
|
|
/* helper is not running or read adjustment call */
|
|
return adjtime(delta, olddelta);
|
|
|
|
memset(&req, 0, sizeof (req));
|
|
req.op = OP_ADJUSTTIME;
|
|
req.data.adjust_time.tv = *delta;
|
|
|
|
submit_request(&req, &res);
|
|
|
|
if (olddelta)
|
|
*olddelta = res.data.adjust_time.tv;
|
|
|
|
return res.rc;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - request ntp_adjtime() */
|
|
|
|
#ifdef PRIVOPS_ADJUSTTIMEX
|
|
int
|
|
PRV_AdjustTimex(struct timex *tmx)
|
|
{
|
|
PrvRequest req;
|
|
PrvResponse res;
|
|
|
|
if (!have_helper())
|
|
return ntp_adjtime(tmx);
|
|
|
|
memset(&req, 0, sizeof (req));
|
|
req.op = OP_ADJUSTTIMEX;
|
|
req.data.adjust_timex.tmx = *tmx;
|
|
|
|
submit_request(&req, &res);
|
|
|
|
*tmx = res.data.adjust_timex.tmx;
|
|
|
|
return res.rc;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - request settimeofday() */
|
|
|
|
#ifdef PRIVOPS_SETTIME
|
|
int
|
|
PRV_SetTime(const struct timeval *tp, const struct timezone *tzp)
|
|
{
|
|
PrvRequest req;
|
|
PrvResponse res;
|
|
|
|
/* only support setting the time */
|
|
assert(tp != NULL);
|
|
assert(tzp == NULL);
|
|
|
|
if (!have_helper())
|
|
return settimeofday(tp, NULL);
|
|
|
|
memset(&req, 0, sizeof (req));
|
|
req.op = OP_SETTIME;
|
|
req.data.set_time.tv = *tp;
|
|
|
|
submit_request(&req, &res);
|
|
|
|
return res.rc;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - request bind() */
|
|
|
|
#ifdef PRIVOPS_BINDSOCKET
|
|
int
|
|
PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len)
|
|
{
|
|
IPSockAddr ip_saddr;
|
|
PrvRequest req;
|
|
PrvResponse res;
|
|
|
|
SCK_SockaddrToIPSockAddr(address, address_len, &ip_saddr);
|
|
if (ip_saddr.port != 0 && ip_saddr.port != CNF_GetNTPPort() &&
|
|
ip_saddr.port != CNF_GetAcquisitionPort() && ip_saddr.port != CNF_GetPtpPort())
|
|
assert(0);
|
|
|
|
if (!have_helper())
|
|
return bind(sock, address, address_len);
|
|
|
|
memset(&req, 0, sizeof (req));
|
|
req.op = OP_BINDSOCKET;
|
|
req.data.bind_socket.sock = sock;
|
|
req.data.bind_socket.sa_len = address_len;
|
|
assert(address_len <= sizeof (req.data.bind_socket.sa));
|
|
memcpy(&req.data.bind_socket.sa.u, address, address_len);
|
|
|
|
submit_request(&req, &res);
|
|
|
|
return res.rc;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - request DNS_Name2IPAddress() */
|
|
|
|
#ifdef PRIVOPS_NAME2IPADDRESS
|
|
int
|
|
PRV_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs)
|
|
{
|
|
PrvRequest req;
|
|
PrvResponse res;
|
|
int i;
|
|
|
|
if (!have_helper())
|
|
return DNS_Name2IPAddress(name, ip_addrs, max_addrs);
|
|
|
|
memset(&req, 0, sizeof (req));
|
|
req.op = OP_NAME2IPADDRESS;
|
|
if (snprintf(req.data.name_to_ipaddress.name, sizeof (req.data.name_to_ipaddress.name),
|
|
"%s", name) >= sizeof (req.data.name_to_ipaddress.name)) {
|
|
return DNS_Failure;
|
|
}
|
|
|
|
submit_request(&req, &res);
|
|
|
|
for (i = 0; i < max_addrs && i < DNS_MAX_ADDRESSES; i++)
|
|
ip_addrs[i] = res.data.name_to_ipaddress.addresses[i];
|
|
|
|
return res.rc;
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - request res_init() */
|
|
|
|
#ifdef PRIVOPS_RELOADDNS
|
|
void
|
|
PRV_ReloadDNS(void)
|
|
{
|
|
PrvRequest req;
|
|
PrvResponse res;
|
|
|
|
if (!have_helper()) {
|
|
DNS_Reload();
|
|
return;
|
|
}
|
|
|
|
memset(&req, 0, sizeof (req));
|
|
req.op = OP_RELOADDNS;
|
|
|
|
submit_request(&req, &res);
|
|
assert(!res.rc);
|
|
}
|
|
#endif
|
|
|
|
/* ======================================================================= */
|
|
|
|
void
|
|
PRV_Initialise(void)
|
|
{
|
|
helper_fd = -1;
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - setup socket(s) then fork to run the helper */
|
|
/* must be called before privileges are dropped */
|
|
|
|
void
|
|
PRV_StartHelper(void)
|
|
{
|
|
pid_t pid;
|
|
int fd, sock_fd1, sock_fd2;
|
|
|
|
if (have_helper())
|
|
LOG_FATAL("Helper already running");
|
|
|
|
sock_fd1 = SCK_OpenUnixSocketPair(SCK_FLAG_BLOCK, &sock_fd2);
|
|
if (sock_fd1 < 0)
|
|
LOG_FATAL("Could not open socket pair");
|
|
|
|
pid = fork();
|
|
if (pid < 0)
|
|
LOG_FATAL("fork() failed : %s", strerror(errno));
|
|
|
|
if (pid == 0) {
|
|
/* child process */
|
|
SCK_CloseSocket(sock_fd1);
|
|
|
|
/* close other descriptors inherited from the parent process, except
|
|
stdin, stdout, and stderr */
|
|
for (fd = STDERR_FILENO + 1; fd < 1024; fd++) {
|
|
if (fd != sock_fd2)
|
|
close(fd);
|
|
}
|
|
|
|
UTI_ResetGetRandomFunctions();
|
|
|
|
/* ignore signals, the process will exit on OP_QUIT request */
|
|
UTI_SetQuitSignalsHandler(SIG_IGN, 1);
|
|
|
|
helper_main(sock_fd2);
|
|
|
|
} else {
|
|
/* parent process */
|
|
SCK_CloseSocket(sock_fd2);
|
|
helper_fd = sock_fd1;
|
|
helper_pid = pid;
|
|
|
|
/* stop the helper even when not exiting cleanly from the main function */
|
|
atexit(stop_helper);
|
|
}
|
|
}
|
|
|
|
/* ======================================================================= */
|
|
|
|
/* DAEMON - graceful shutdown of the helper */
|
|
|
|
void
|
|
PRV_Finalise(void)
|
|
{
|
|
if (!have_helper())
|
|
return;
|
|
|
|
stop_helper();
|
|
close(helper_fd);
|
|
helper_fd = -1;
|
|
}
|