[chrony-dev] Re: [PATCH] Privilege Separation - Version 2 - Add helper process |
[ Thread Index |
Date Index
| More chrony.tuxfamily.org/chrony-dev Archives
]
- To: chrony-dev@xxxxxxxxxxxxxxxxxxxx
- Subject: [chrony-dev] Re: [PATCH] Privilege Separation - Version 2 - Add helper process
- From: Bryan Christianson <bryan@xxxxxxxxxxxxx>
- Date: Mon, 16 Nov 2015 13:42:27 +1300
- Dkim-signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=smtpcorp.com; s=a0-2; h=Feedback-ID:X-Smtpcorp-Track:To:Message-Id:Date: From:Subject; bh=yDOMSkmgrruVPCdpt7UIlaDFPmC6SmfH9p/6xL9LQEw=; b=iUN8tEUL8/1R z7jcKMtF/2uVK6CXMd3zQ5mRai4kqPLm4348X95bnpOIEvYHnfqYKK6yfT3qqHrvPDVkSSUAUN67v Y1op633kFoY7RLAbkNth4K8NKH51xpx5kYThR1g5Qi9agwHBPFpYMbi8nPd7uaTLkz0DWKSSM5m9O C2k5ebCwJrpLXi1VMQ5z9mGbak7gpBa8m83mEY/Z347iLRVGyL5NsIRjmqdtKp/ZsAiFZhysUyJ4U CpF1DUsDi5pC0Bs6eiEjrJUMSIVwhkOu++BIUgK3zc2sO2yi5r8XVR83REXMKBgnmY+LTBucQbOE0 qb8oJlNM8OKcIbxwJAzNLQ==;
- Feedback-id: 149811m:149811acx33YQ:149811spPDnDkWAs:SMTPCORP
I removed all the stuff to actually compile privops, figuring it would show up when the priv helper is used with a driver.
I have a couple of queries about the next step
1. PRIVOPS_ALL is true when any of the function defines is added to config, I think PRV_Initialise() should be called
from SYS_DropRoot() (guarded by PRIVOPS_ALL) - or would you prefer a call from each driver SYS_xxxxx_DropRoot() ?
2. Not sure where (or even if) PRV_Finalise() should be called.
--
Bryan
> On 16/11/2015, at 12:53 PM, Bryan Christianson <bryan@xxxxxxxxxxxxx> wrote:
>
> Privileged helper that will perform adjtime(), settimeofday(), bind() on
> behalf of chronyd when running as non-root user.
>
> Changes since version 1
> 1. Fix variable/struct/function names as suggested
> 2. Avoid calling LOG_FATAL from the helper
> 3. Added #define PRIVOPS_xxxxx for each privileged function
> 4. Transfer fd request and data in single I/O
> 5. helper_main() restructured as suggested.
> 6. remove privops.o from Makefile.in
>
> Tested compilation and execution on MacOS X
> ---
> logging.h | 1 +
> privops.c | 511 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> privops.h | 59 ++++++++
> 3 files changed, 571 insertions(+)
> create mode 100644 privops.c
> create mode 100644 privops.h
>
> diff --git a/logging.h b/logging.h
> index b480c42..8fc8640 100644
> --- a/logging.h
> +++ b/logging.h
> @@ -100,6 +100,7 @@ typedef enum {
> LOGF_Keys,
> LOGF_Logging,
> LOGF_Nameserv,
> + LOGF_PrivOps,
> LOGF_Rtc,
> LOGF_Regress,
> LOGF_Sys,
> diff --git a/privops.c b/privops.c
> new file mode 100644
> index 0000000..5b49b53
> --- /dev/null
> +++ b/privops.c
> @@ -0,0 +1,511 @@
> +/*
> + chronyd/chronyc - Programs for keeping computer clocks accurate.
> +
> + **********************************************************************
> + * Copyright (C) Bryan Christianson 2015
> + *
> + * 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 "logging.h"
> +#include "privops.h"
> +#include "util.h"
> +
> +#if PRIVOPS_ANY
> +
> +#define op_ADJTIME 1024
> +#define op_SETTIMEOFDAY 1025
> +#define op_BINDSOCKET 1026
> +
> +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;
> +
> +typedef struct {
> + struct timeval tv;
> +} ReqSetTime;
> +
> +typedef struct {
> + int sock;
> + socklen_t sa_len;
> + union sockaddr_in46 sa;
> +} ReqBindSocket;
> +
> +typedef struct {
> + int op;
> + union {
> + ReqAdjustTime adj_tv;
> + ReqSetTime settime_tv;
> + ReqBindSocket bind_sock;
> + } u;
> +} PrvRequest;
> +
> +/* helper response structs */
> +
> +typedef struct {
> + struct timeval tv;
> +} ResAdjustTime;
> +
> +typedef struct {
> + char msg[256];
> +} ResFatalMsg;
> +
> +typedef struct {
> + int fatal_error;
> + int rc;
> + int res_errno;
> + union {
> + ResFatalMsg fatal_msg;
> + ResAdjustTime adj_tv;
> + } u;
> +} PrvResponse;
> +
> +static int helper_fd = -1;
> +
> +static inline int
> +have_helper(void)
> +{
> + return helper_fd >= 0;
> +}
> +
> +/* ======================================================================= */
> +
> +/* send an open file descriptor plus data over a unix socket */
> +
> +static int
> +send_with_fd(int fd, int fd_to_send, const void *data, int data_len)
> +{
> + struct msghdr msg;
> + struct iovec iov;
> + int cmsglen;
> + struct cmsghdr *cmsg;
> + void *ptr_send_fd;
> + char cmsgbuf[256];
> +
> + /* data is transferred vio iov */
> + iov.iov_base = (void *)data;
> + iov.iov_len = data_len;
> +
> + msg.msg_name = NULL;
> + msg.msg_namelen = 0;
> + msg.msg_iov = &iov;
> + msg.msg_iovlen = 1;
> + msg.msg_control = cmsgbuf;
> + msg.msg_controllen = sizeof(cmsgbuf);
> + msg.msg_flags = 0;
> +
> + cmsglen = 0;
> +
> + cmsg = CMSG_FIRSTHDR(&msg);
> + memset(cmsg, 0, CMSG_SPACE(sizeof(int)));
> + cmsglen += CMSG_SPACE(sizeof(int));
> +
> + cmsg->cmsg_level = SOL_SOCKET;
> + cmsg->cmsg_type = SCM_RIGHTS;
> + cmsg->cmsg_len = CMSG_LEN(sizeof(int));
> +
> + ptr_send_fd = CMSG_DATA(cmsg);
> + memcpy(ptr_send_fd, &fd_to_send, sizeof(int));
> +
> + msg.msg_controllen = cmsglen;
> +
> + if (sendmsg(fd, &msg, 0) != data_len)
> + return -1;
> +
> + return 0;
> +}
> +
> +/* ======================================================================= */
> +/* receive data plus optional file descriptor over a unix socket */
> +
> +static int
> +receive_with_fd(int fd, void *data, int data_size, int *fd_recvd)
> +{
> + int data_bytes;
> + struct msghdr msg;
> + struct cmsghdr *cmsg;
> + struct iovec iov;
> + char cmsgbuf[256];
> +
> + *fd_recvd = -1;
> +
> + iov.iov_base = data;
> + iov.iov_len = data_size;
> +
> + msg.msg_name = NULL;
> + msg.msg_namelen = 0;
> + msg.msg_iov = &iov;
> + msg.msg_iovlen = 1;
> + msg.msg_control = (void *)cmsgbuf;
> + msg.msg_controllen = sizeof(cmsgbuf);
> + msg.msg_flags = MSG_WAITALL;
> +
> + /* read the data */
> + data_bytes = recvmsg(fd, &msg, 0);
> + if (data_bytes <= 0)
> + return -1;
> +
> + /* extract transferred descriptor (if any) */
> + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
> + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
> + memcpy(fd_recvd, CMSG_DATA(cmsg), sizeof(int));
> + }
> +
> + return data_bytes;
> +}
> +
> +/* ======================================================================= */
> +
> +/* HELPER - prepare fatal error for daemon */
> +static void
> +res_fatal(PrvResponse *res, const char *fmt, ...)
> +{
> + va_list ap;
> + va_start(ap, fmt);
> +
> + memset(res, 0, sizeof(PrvResponse));
> + res->fatal_error = 1;
> + vsnprintf(res->u.fatal_msg.msg, sizeof(ResFatalMsg), fmt, ap);
> +
> + va_end(ap);
> +}
> +
> +/* ======================================================================= */
> +
> +/* HELPER - send response to the fd */
> +
> +static inline int
> +send_response(int fd, const PrvResponse *res)
> +{
> + if (write(fd, res, sizeof(PrvResponse)) != sizeof(PrvResponse))
> + return -1;
> +
> + return 0;
> +}
> +
> +/* ======================================================================= */
> +
> +/* HELPER - perform adjtime() */
> +
> +static void
> +do_adjtime(const PrvRequest *req, PrvResponse *res)
> +{
> + const struct timeval *delta = &req->u.adj_tv.tv;
> + struct timeval olddelta;
> +
> + memset(res, 0, sizeof(PrvResponse));
> + res->rc = adjtime(delta, &olddelta);
> + if (res->rc != 0)
> + res->res_errno = errno;
> + else
> + res->u.adj_tv.tv = olddelta;
> +}
> +
> +/* ======================================================================= */
> +
> +/* HELPER - perform settimeofday() */
> +
> +static void
> +do_settimeofday(const PrvRequest *req, PrvResponse *res)
> +{
> + const struct timeval *tv = &req->u.adj_tv.tv;
> + memset(res, 0, sizeof(PrvResponse));
> +
> + res->rc = settimeofday(tv, NULL);
> + if (res->rc != 0)
> + res->res_errno = errno;
> +}
> +
> +/* ======================================================================= */
> +
> +/* HELPER - bind port to a socket */
> +static void
> +do_bindsocket(PrvRequest *req, PrvResponse *res)
> +{
> + unsigned short port;
> + IPAddr ip;
> + int sock_fd;
> + struct sockaddr *sa;
> + socklen_t sa_len;
> +
> + sa = &req->u.bind_sock.sa.u;
> + sa_len = req->u.bind_sock.sa_len;
> + sock_fd = req->u.bind_sock.sock;
> +
> + UTI_SockaddrToIPAndPort(sa, &ip, &port);
> + if (port != 0 && port != CNF_GetNTPPort()) {
> + res_fatal(res, "PRV_BindSocket, invalid port: %d", port);
> + return;
> + }
> +
> + memset(res, 0, sizeof(PrvResponse));
> + res->rc = bind(sock_fd, sa, sa_len);
> + if (res->rc != 0)
> + res->res_errno = errno;
> +
> + /* sock is still open on daemon side but we're done with it in the helper */
> + close(sock_fd);
> +}
> +
> +/* ======================================================================= */
> +
> +/* HELPER - main loop - action requests from the daemon */
> +
> +static void
> +helper_main(int fd)
> +{
> + PrvRequest req;
> + PrvResponse res;
> + int recvd_fd;
> +
> + while (1) {
> + if (receive_with_fd(fd, &req, sizeof(PrvRequest), &recvd_fd) < sizeof(PrvRequest))
> + /* read error or closed input - we cannot recover - give up */
> + return;
> +
> + switch (req.op) {
> + default:
> + res_fatal(&res, "Unexpected operator : %d", req.op);
> + break;
> +
> + case op_ADJTIME:
> + do_adjtime(&req, &res);
> + break;
> +
> + case op_SETTIMEOFDAY:
> + do_settimeofday(&req, &res);
> + break;
> +
> + case op_BINDSOCKET:
> + req.u.bind_sock.sock = recvd_fd; /* use transferred descriptor */
> + do_bindsocket(&req, &res);
> + break;
> + }
> + send_response(fd, &res);
> + }
> +}
> +
> +/* ======================================================================= */
> +
> +/* DAEMON - read helper response from fd */
> +
> +static int
> +read_response(int fd, PrvResponse *res)
> +{
> + if (read(fd, res, sizeof(PrvResponse)) < sizeof(PrvResponse))
> + LOG_FATAL(LOGF_PrivOps, "Error reading from helper fd : %s", strerror(errno));
> +
> + if (res->fatal_error)
> + LOG_FATAL(LOGF_PrivOps, "Fatal error in helper: %s", res->u.fatal_msg.msg);
> +
> + /* if operation failed in the helper, set errno so daemon can print log message */
> + if (res->rc != 0) {
> + errno = res->res_errno;
> + return -1;
> + }
> + return 0;
> +}
> +
> +/* ======================================================================= */
> +
> +/* DAEMON - send daemon request to fd and wait for response */
> +
> +static int
> +send_to_helper(int fd, const PrvRequest *req, PrvResponse *res)
> +{
> + int error;
> +
> + if (req->op == op_BINDSOCKET)
> + error = send_with_fd(fd, req->u.bind_sock.sock, req, sizeof(PrvRequest)) != 0;
> + else
> + error = write(fd, req, sizeof(PrvRequest)) != sizeof(PrvRequest);
> +
> + if (error)
> + LOG_FATAL(LOGF_PrivOps, "Error writing to helper fd : %s", strerror(errno));
> +
> + return read_response(fd, res);
> +}
> +
> +/* ======================================================================= */
> +
> +/* DAEMON - bind socket to reserved port */
> +
> +#if defined(PRIVOPS_BINDSOCKET)
> +int
> +PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len)
> +{
> + PrvRequest req;
> + PrvResponse res;
> + IPAddr ip;
> + unsigned short port;
> +
> + if (address->sa_len > address_len)
> + LOG_FATAL(LOGF_PrivOps, "PRV_BindSocket, incorrect address length: %d", (int)address_len);
> +
> + UTI_SockaddrToIPAndPort(address, &ip, &port);
> + if (port != 0 && port != CNF_GetNTPPort())
> + LOG_FATAL(LOGF_PrivOps, "PRV_BindSocket, invalid port: %d", port);
> +
> + if (!have_helper())
> + /* dont use the helper if not required */
> + return bind(sock, address, address_len);
> +
> + req.op = op_BINDSOCKET;
> + req.u.bind_sock.sock = sock;
> + req.u.bind_sock.sa_len = address_len;
> + memcpy(&req.u.bind_sock.sa.u, address, address_len);
> +
> + if (send_to_helper(helper_fd, &req, &res) != 0)
> + return -1;
> +
> + return 0;
> +}
> +#endif
> +
> +/* ======================================================================= */
> +
> +/* DAEMON - request adjtime() */
> +
> +#if defined(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);
> +
> + req.op = op_ADJTIME;
> + req.u.adj_tv.tv = *delta;
> +
> + if (send_to_helper(helper_fd, &req, &res) != 0)
> + return -1;
> +
> + if (olddelta)
> + *olddelta = res.u.adj_tv.tv;
> +
> + return 0;
> +}
> +#endif
> +
> +/* ======================================================================= */
> +
> +/* DAEMON - request settimeofday() */
> +
> +#if defined(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()) {
> + /* helper is not running - this will fail if not privileged */
> + return settimeofday(tp, NULL);
> + }
> +
> + req.op = op_SETTIMEOFDAY;
> + req.u.settime_tv.tv = *tp;
> +
> + return send_to_helper(helper_fd, &req, &res);
> +}
> +#endif
> +
> +/* ======================================================================= */
> +
> +/* DAEMON - setup socket(s) then fork to run the helper */
> +/* must be called before privileges are dropped */
> +
> +void
> +PRV_Initialise(void)
> +{
> + pid_t pid;
> + int sock_pair[2];
> +
> + if (have_helper())
> + LOG_FATAL(LOGF_PrivOps, "PRV_start, helper is already running");
> +
> + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair) != 0)
> + LOG_FATAL(LOGF_PrivOps, "PRV_start, socketpair() failed : %s", strerror(errno));
> +
> + UTI_FdSetCloexec(sock_pair[0]);
> + UTI_FdSetCloexec(sock_pair[1]);
> +
> + pid = fork();
> + if (pid == -1)
> + LOG_FATAL(LOGF_PrivOps, "PRV_start, fork failed : %s", strerror(errno));
> +
> + if (pid == 0) {
> + /* child process */
> + int fd;
> + close(sock_pair[0]);
> +
> + /* close other descriptors inherited from the parent process */
> + for (fd = 0; fd < 1024; fd++) {
> + if (fd != sock_pair[1])
> + close(fd);
> + }
> +
> + helper_main(sock_pair[1]);
> +
> + close(sock_pair[1]);
> + exit(0);
> +
> + } else {
> + /* parent process */
> + close(sock_pair[1]);
> + helper_fd = sock_pair[0];
> + }
> +}
> +
> +/* ======================================================================= */
> +
> +/* DAEMON - graceful shutdown of the helper */
> +
> +void
> +PRV_Finalise(void)
> +{
> + int status;
> + close(helper_fd);
> + helper_fd = -1;
> + wait(&status);
> +}
> +
> +#endif /* PRIVOPS_ANY */
> diff --git a/privops.h b/privops.h
> new file mode 100644
> index 0000000..d699deb
> --- /dev/null
> +++ b/privops.h
> @@ -0,0 +1,59 @@
> +/*
> + chronyd/chronyc - Programs for keeping computer clocks accurate.
> +
> + **********************************************************************
> + * Copyright (C) Bryan Christianson 2015
> + *
> + * 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.
> +
> +*/
> +
> +#ifndef GOT_PRIVOPS_H
> +#define GOT_PRIVOPS_H
> +
> +#define PRIVOPS_ANY \
> + defined(PRIVOPS_ADJUSTTIME) || \
> + defined(PRIVOPS_BINDSOCKET) || \
> + defined(PRIVOPS_SETTIME)
> +
> +#if defined(PRIVOPS_ADJUSTTIME)
> + int PRV_AdjustTime(const struct timeval *delta, struct timeval *olddelta);
> +#else
> + #define PRV_AdjustTime adjtime
> +#endif
> +
> +#if defined(PRIVOPS_BINDSOCKET)
> + int PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len);
> +#else
> + #define PRV_BindSocket bind
> +#endif
> +
> +#if defined(PRIVOPS_SETTIME)
> + int PRV_SetTime(const struct timeval *tp, const struct timezone *tzp);
> +#else
> + #define PRV_SetTime settimeofday
> +#endif
> +
> +#if PRIVOPS_ANY
> + void PRV_Initialise(void);
> + void PRV_Finalise(void);
> +#endif
> +
> +#endif
> --
> 2.4.9 (Apple Git-60)
>
--
To unsubscribe email chrony-dev-request@xxxxxxxxxxxxxxxxxxxx with "unsubscribe" in the subject.
For help email chrony-dev-request@xxxxxxxxxxxxxxxxxxxx with "help" in the subject.
Trouble? Email listmaster@xxxxxxxxxxxxxxxxxxxx.