]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
client: add support for dropping root privileges
authorMiroslav Lichvar <mlichvar@redhat.com>
Mon, 4 Aug 2025 14:34:52 +0000 (16:34 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Thu, 7 Aug 2025 08:18:31 +0000 (10:18 +0200)
To minimize the impact of potential attacks targeting chronyc started
under root (e.g. performed by a local chronyd process running without
root privileges, a remote chronyd process, or a MITM attacker on the
network), add support for changing the effective UID/GID in chronyc
after start.

The user can be specified by the -u option, similarly to chronyd. The
default chronyc user can be changed by the --with-chronyc-user
configure option. The default value of the default chronyc user is
"root", i.e. chronyc doesn't try to change the identity by default.

The default chronyc user does not follow the default chronyd user
set by the configure --with-user option to avoid errors on systems where
chronyc is not allowed to change its UID/GID (e.g. by a SELinux policy).

client.c
configure
doc/Makefile.in
doc/chronyc.adoc
test/system/test.common

index c5898d10cf12ffe6f1d0829c72767f326566a1fc..1701dfd0301f41f4ce778f834a56a5de8b3caf9b 100644 (file)
--- a/client.c
+++ b/client.c
@@ -3590,9 +3590,11 @@ print_help(const char *progname)
              "  -m\t\tAccept multiple commands\n"
              "  -h HOST\tSpecify server (%s)\n"
              "  -p PORT\tSpecify UDP port (%d)\n"
+             "  -u USER\tSpecify user (%s)\n"
              "  -v, --version\tPrint version and exit\n"
              "      --help\tPrint usage and exit\n",
-             progname, DEFAULT_COMMAND_SOCKET",127.0.0.1,::1", DEFAULT_CANDM_PORT);
+             progname, DEFAULT_COMMAND_SOCKET",127.0.0.1,::1",
+             DEFAULT_CANDM_PORT, DEFAULT_CHRONYC_USER);
 }
 
 /* ================================================== */
@@ -3609,10 +3611,11 @@ int
 main(int argc, char **argv)
 {
   char *line;
+  const char *hostnames = NULL, *user = DEFAULT_CHRONYC_USER;
   const char *progname = argv[0];
-  const char *hostnames = NULL;
   int opt, ret = 1, multi = 0, family = IPADDR_UNSPEC;
   int port = DEFAULT_CANDM_PORT;
+  struct passwd *pw;
 
   /* Parse long command-line options */
   for (optind = 1; optind < argc; optind++) {
@@ -3628,7 +3631,7 @@ main(int argc, char **argv)
   optind = 1;
 
   /* Parse short command-line options */
-  while ((opt = getopt(argc, argv, "+46acdef:h:mnNp:v")) != -1) {
+  while ((opt = getopt(argc, argv, "+46acdef:h:mnNp:u:v")) != -1) {
     switch (opt) {
       case '4':
       case '6':
@@ -3664,6 +3667,9 @@ main(int argc, char **argv)
       case 'p':
         port = atoi(optarg);
         break;
+      case 'u':
+        user = optarg;
+        break;
       case 'v':
         print_version();
         return 0;
@@ -3673,6 +3679,14 @@ main(int argc, char **argv)
     }
   }
 
+  /* Drop root privileges if configured to do so */
+  if (strcmp(user, "root") != 0 && geteuid() == 0) {
+    pw = getpwnam(user);
+    if (!pw)
+      LOG_FATAL("Could not get user/group ID of %s", user);
+    UTI_DropRoot(pw->pw_uid, pw->pw_gid);
+  }
+
   if (isatty(0) && isatty(1) && isatty(2)) {
     on_terminal = 1;
   }
index 7f978c612f5f1f1e934200a3fdeec75485ebee9e..58159de58eb0eabd3c181117165715c221eaa7be 100755 (executable)
--- a/configure
+++ b/configure
@@ -133,6 +133,7 @@ For better control, use the options below.
   --with-ntp-era=SECONDS Specify earliest assumed NTP time in seconds
                          since 1970-01-01 [50*365 days ago]
   --with-user=USER       Specify default chronyd user [root]
+  --with-chronyc-user=USER Specify default chronyc user [root]
   --with-hwclockfile=PATH Specify default path to hwclock(8) adjtime file
   --with-pidfile=PATH    Specify default pidfile [/var/run/chrony/chronyd.pid]
   --with-rtcdevice=PATH  Specify default path to RTC device [/dev/rtc]
@@ -250,6 +251,7 @@ try_timestamping=0
 feat_ntp_signd=0
 ntp_era_split=""
 default_user="root"
+default_chronyc_user="root"
 default_hwclockfile=""
 default_pidfile="/var/run/chrony/chronyd.pid"
 default_rtcdevice="/dev/rtc"
@@ -354,6 +356,9 @@ do
     --with-user=* )
       default_user=`echo $option | sed -e 's/^.*=//;'`
     ;;
+    --with-chronyc-user=* )
+      default_chronyc_user=`echo $option | sed -e 's/^.*=//;'`
+    ;;
     --with-hwclockfile=* )
       default_hwclockfile=`echo $option | sed -e 's/^.*=//;'`
     ;;
@@ -1080,6 +1085,7 @@ add_def DEFAULT_HWCLOCK_FILE "\"$default_hwclockfile\""
 add_def DEFAULT_PID_FILE "\"$default_pidfile\""
 add_def DEFAULT_RTC_DEVICE "\"$default_rtcdevice\""
 add_def DEFAULT_USER "\"$default_user\""
+add_def DEFAULT_CHRONYC_USER "\"$default_chronyc_user\""
 add_def DEFAULT_COMMAND_SOCKET "\"$CHRONYRUNDIR/chronyd.sock\""
 add_def MAIL_PROGRAM "\"$mail_program\""
 
@@ -1123,6 +1129,7 @@ do
           s%@DEFAULT_PID_FILE@%${default_pidfile}%;\
           s%@DEFAULT_RTC_DEVICE@%${default_rtcdevice}%;\
           s%@DEFAULT_USER@%${default_user}%;\
+          s%@DEFAULT_CHRONYC_USER@%${default_chronyc_user}%;\
           s%@CHRONY_VERSION@%${CHRONY_VERSION}%;" \
           < ${f}.in > $f
 done
index 1777da58a5b8ff2a4b66834b7c4068cb0f71a300..a8ce3c773dd815b10487c9f44283ee4fbbbe2508 100644 (file)
@@ -17,6 +17,7 @@ CHRONYRUNDIR = @CHRONYRUNDIR@
 CHRONYVARDIR = @CHRONYVARDIR@
 CHRONY_VERSION = @CHRONY_VERSION@
 DEFAULT_USER = @DEFAULT_USER@
+DEFAULT_CHRONYC_USER = @DEFAULT_CHRONYC_USER@
 DEFAULT_HWCLOCK_FILE = @DEFAULT_HWCLOCK_FILE@
 DEFAULT_PID_FILE = @DEFAULT_PID_FILE@
 DEFAULT_RTC_DEVICE = @DEFAULT_RTC_DEVICE@
@@ -29,6 +30,7 @@ SED_COMMANDS = "s%\@SYSCONFDIR\@%$(SYSCONFDIR)%g;\
               s%\@DEFAULT_PID_FILE\@%$(DEFAULT_PID_FILE)%g;\
               s%\@DEFAULT_RTC_DEVICE\@%$(DEFAULT_RTC_DEVICE)%g;\
               s%\@DEFAULT_USER\@%$(DEFAULT_USER)%g;\
+              s%\@DEFAULT_CHRONYC_USER\@%$(DEFAULT_CHRONYC_USER)%g;\
               s%\@CHRONYRUNDIR\@%$(CHRONYRUNDIR)%g;\
               s%\@CHRONYVARDIR\@%$(CHRONYVARDIR)%g;"
 
index 65040ef7d5bc5d17432a097a9438e40b64969df7..36790dad11b9d51071200d742ef772c7b21232d7 100644 (file)
@@ -121,6 +121,13 @@ This option allows the user to specify the UDP port number which the target
 *chronyd* is using for its monitoring connections. This defaults to 323; there
 would rarely be a need to change this.
 
+*-u* _user_::
+This option sets the name of the user to which *chronyc* will switch when it is
+started under the root user in order to drop its privileges. *chronyc* will not
+try to change its identity if the option is set to _root_, or the effective
+user ID of the started process is not 0. The compiled-in default value is
+_@DEFAULT_CHRONYC_USER@_.
+
 *-f* _file_::
 This option is ignored and is provided only for compatibility.
 
index e4d9fb6cd3ace4f49a1dca3f1df534f9e28f502c..07f259d6204081b91b87948e7d79bd5e909ceb6f 100644 (file)
@@ -390,7 +390,7 @@ run_chronyc() {
        fi
 
        $CHRONYC_WRAPPER $wrapper_options \
-               "$chronyc" -h "$host" $options "$@" > "$TEST_DIR/chronyc.out" && \
+               "$chronyc" -h "$host" -u "$(get_user)" $options "$@" > "$TEST_DIR/chronyc.out" && \
                        test_ok || test_error
        [ $? -ne 0 ] && ret=1