[chrony-dev] [PATCH] Privilege Separation - Version 4 - Add helper process

[ Thread Index | Date Index | More chrony.tuxfamily.org/chrony-dev Archives ]


Privileged helper that will perform adjtime(), settimeofday(), bind() on
behalf of chronyd when running as non-root user.

Changes since version 3
1. No need to clear response struct on fatal error for 2nd time
2. Return receive error if file descriptor not found in bind operation
3. Conditional compilation for adjtime() call in helper
4. Close potentially leaked descriptor if invalid port detected in call to bind.
5. Add DEBUG messages for request/response
6. Clear request memory before use.
---
 logging.h |   1 +
 privops.c | 497 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 privops.h |  54 +++++++
 sysincl.h |   1 +
 4 files changed, 553 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..4d471ee
--- /dev/null
+++ b/privops.c
@@ -0,0 +1,497 @@
+/*
+  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"
+
+#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 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;
+  va_start(ap, fmt);
+
+  res->fatal_error = 1;
+  vsnprintf(res->u.fatal_msg.msg, sizeof(ResFatalMsg), fmt, ap);
+
+  va_end(ap);
+}
+
+/* ======================================================================= */
+
+/* HELPER - send response to the fd */
+
+static int
+send_response(int fd, const PrvResponse *res)
+{
+  if (write(fd, res, sizeof(PrvResponse)) != sizeof(PrvResponse))
+    return 0;
+
+  return 1;
+}
+
+/* ======================================================================= */
+/* receive daemon request plus optional file descriptor over a unix socket */
+
+static int
+receive_from_daemon(int fd, PrvRequest *req)
+{
+  int data_bytes;
+  struct msghdr msg;
+  struct cmsghdr *cmsg;
+  struct iovec iov;
+  char cmsgbuf[256];
+
+  iov.iov_base = req;
+  iov.iov_len = sizeof(PrvRequest);
+
+  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 0;
+
+  if (req->op == op_BINDSOCKET) {
+    /* extract transferred descriptor */
+    req->u.bind_sock.sock = -1;
+    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+      if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
+        memcpy(&req->u.bind_sock.sock, CMSG_DATA(cmsg), sizeof(int));
+    }
+    if (req->u.bind_sock.sock < 0) {
+      /* return error if valid descriptor not found */
+      return 0;
+    }
+  }
+
+  return data_bytes == sizeof(PrvRequest);
+}
+
+#if defined(PRIVOPS_ADJUSTTIME)
+/* ======================================================================= */
+
+/* HELPER - perform adjtime() */
+
+static void
+do_adjtime(const ReqAdjustTime *req, PrvResponse *res)
+{
+  const struct timeval *delta = &req->tv;
+  struct timeval olddelta;
+
+  res->rc = adjtime(delta, &olddelta);
+  if (res->rc != 0)
+    res->res_errno = errno;
+  else
+    res->u.adj_tv.tv = olddelta;
+}
+#endif
+
+/* ======================================================================= */
+
+/* HELPER - perform settimeofday() */
+
+static void
+do_settimeofday(const ReqSetTime *req, PrvResponse *res)
+{
+  const struct timeval *tv = &req->tv;
+
+  res->rc = settimeofday(tv, NULL);
+  if (res->rc != 0)
+    res->res_errno = errno;
+}
+
+/* ======================================================================= */
+
+/* HELPER - bind port to a socket */
+static void
+do_bindsocket(ReqBindSocket *req, PrvResponse *res)
+{
+  unsigned short port;
+  IPAddr ip;
+  int sock_fd;
+  struct sockaddr *sa;
+  socklen_t sa_len;
+
+  sa = &req->sa.u;
+  sa_len = req->sa_len;
+  sock_fd = req->sock;
+
+  UTI_SockaddrToIPAndPort(sa, &ip, &port);
+  if (port != 0 && port != CNF_GetNTPPort()) {
+    close(sock_fd);
+    res_fatal(res, "PRV_BindSocket, invalid port: %d", port);
+    return;
+  }
+
+  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;
+
+  while (1) {
+    if (!receive_from_daemon(fd, &req))
+      /* read error or closed input - we cannot recover - give up */
+      break;
+
+    memset(&res, 0, sizeof(PrvResponse));
+    switch (req.op) {
+#if defined(PRIVOPS_ADJUSTTIME)
+    case op_ADJTIME:
+      do_adjtime(&req.u.adj_tv, &res);
+      break;
+#endif
+    case op_SETTIMEOFDAY:
+      do_settimeofday(&req.u.settime_tv, &res);
+      break;
+
+    case op_BINDSOCKET:
+      do_bindsocket(&req.u.bind_sock, &res);
+      break;
+
+    default:
+      res_fatal(&res, "Unexpected operator : %d", req.op);
+      break;
+    }
+    send_response(fd, &res);
+  }
+
+  close(fd);
+  exit(0);
+}
+
+/* ======================================================================= */
+
+/* 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));
+    
+  DEBUG_LOG(LOGF_PrivOps, "Received response : fatal_error: %d rc: %d",
+                res->fatal_error, res->rc);
+
+  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 0;
+  }
+  return 1;
+}
+
+/* ======================================================================= */
+
+/* DAEMON - send daemon request to fd and wait for response */
+
+static int
+send_to_helper(int fd, PrvRequest *req, PrvResponse *res)
+{
+  struct msghdr msg;
+  struct iovec iov;
+  char cmsgbuf[256];
+
+  iov.iov_base = req;
+  iov.iov_len = sizeof(PrvRequest);
+
+  msg.msg_name = NULL;
+  msg.msg_namelen = 0;
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+  msg.msg_control = NULL;
+  msg.msg_controllen = 0;
+  msg.msg_flags = 0;
+
+  if (req->op == op_BINDSOCKET) {
+    /* send file descriptor as a control message */
+    struct cmsghdr *cmsg;
+
+    msg.msg_control = cmsgbuf;
+    msg.msg_controllen = CMSG_SPACE(sizeof(int));
+
+    cmsg = CMSG_FIRSTHDR(&msg);
+    memset(cmsg, 0, CMSG_SPACE(sizeof(int)));
+
+    cmsg->cmsg_level = SOL_SOCKET;
+    cmsg->cmsg_type = SCM_RIGHTS;
+    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+
+    *((int *)CMSG_DATA(cmsg)) = req->u.bind_sock.sock;
+  }
+
+  if (sendmsg(fd, &msg, 0) != sizeof(PrvRequest))
+    LOG_FATAL(LOGF_PrivOps, "Error writing to helper fd : %s", strerror(errno));
+
+  DEBUG_LOG(LOGF_PrivOps, "Sent request: op: %d", req->op);
+  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;
+
+  UTI_SockaddrToIPAndPort(address, &ip, &port);
+  if (port != 0 && port != CNF_GetNTPPort())
+    LOG_FATAL(LOGF_PrivOps, "Invalid port: %d", port);
+
+  if (!have_helper())
+    /* dont use the helper if not required */
+    return bind(sock, address, address_len);
+
+  memset(&req, 0, sizeof(req));
+  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);
+
+  memset(&req, 0, sizeof(req));
+  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);
+  }
+
+  memset(&req, 0, sizeof(req));
+  req.op = op_SETTIMEOFDAY;
+  req.u.settime_tv.tv = *tp;
+
+  if (send_to_helper(helper_fd, &req, &res) == 0)
+    return -1;
+
+  return 0;
+}
+#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, "Helper is already running");
+
+  if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sock_pair) != 0)
+    LOG_FATAL(LOGF_PrivOps, "socketpair() failed : %s", strerror(errno));
+
+  UTI_FdSetCloexec(sock_pair[0]);
+  UTI_FdSetCloexec(sock_pair[1]);
+
+  pid = fork();
+  if (pid == -1)
+    LOG_FATAL(LOGF_PrivOps, "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]);
+
+  } 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);
+}
+
diff --git a/privops.h b/privops.h
new file mode 100644
index 0000000..b56ebcf
--- /dev/null
+++ b/privops.h
@@ -0,0 +1,54 @@
+/*
+  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
+
+#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 defined(PRIVOPS_HELPER)
+  void PRV_Initialise(void);
+  void PRV_Finalise(void);
+#endif
+
+#endif
diff --git a/sysincl.h b/sysincl.h
index 30e9b48..7dbc8f2 100644
--- a/sysincl.h
+++ b/sysincl.h
@@ -53,6 +53,7 @@
 #include <sys/types.h>
 #include <sys/un.h>
 #include <sys/shm.h>
+#include <sys/wait.h>
 #include <syslog.h>
 #include <time.h>
 #include <unistd.h>
-- 
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.


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/