Browse code

bb12597: Fix daemon switch user issues

Drop privileges in the parent process before waiting for the signal
from the child process, so that the parent properly responds to
the signal and terminates.

Verify that the log file will be owned by user that the deamon will
run as.

Explicitly set PID file ownership to root when starting the daemon
as root with the PID file enabled.

Andy Ragusa (aragusa) authored on 2020/09/01 03:34:05
Showing 8 changed files
... ...
@@ -90,9 +90,13 @@ int main(int argc, char **argv)
90 90
     time_t currtime;
91 91
     mode_t umsk;
92 92
     pid_t parentPid = getpid();
93
+#ifndef _WIN32
94
+    int dropPrivRet = 0;
95
+#endif /* _WIN32 */
93 96
 
94 97
     sigset_t sigset;
95 98
     struct sigaction act;
99
+    const char * user_name = NULL;
96 100
 
97 101
     cl_initialize_crypto();
98 102
 
... ...
@@ -154,6 +158,10 @@ int main(int argc, char **argv)
154 154
     }
155 155
     free(pt);
156 156
 
157
+    if ((opt = optget(opts, "User"))->enabled){
158
+        user_name = opt->strarg;
159
+    }
160
+
157 161
     if ((opt = optget(opts, "Chroot"))->enabled) {
158 162
         if (chdir(opt->strarg) != 0) {
159 163
             logg("!Cannot change directory to %s\n", opt->strarg);
... ...
@@ -252,11 +260,11 @@ int main(int argc, char **argv)
252 252
             }
253 253
         }
254 254
 
255
-        if ((opt = optget(opts, "User"))->enabled) {
255
+        if (NULL != user_name) {
256 256
             struct passwd *user;
257
-            if ((user = getpwnam(opt->strarg)) == NULL) {
257
+            if ((user = getpwnam(user_name)) == NULL) {
258 258
                 logg("ERROR: Can't get information about user %s.\n",
259
-                     opt->strarg);
259
+                     user_name);
260 260
                 logg_close();
261 261
                 optfree(opts);
262 262
                 return 1;
... ...
@@ -360,7 +368,7 @@ int main(int argc, char **argv)
360 360
 
361 361
 #ifndef _WIN32
362 362
     if (!optget(opts, "Foreground")->enabled) {
363
-        if (-1 == daemonize_parent_wait()) {
363
+        if (-1 == daemonize_parent_wait(user_name, logg_file)) {
364 364
             logg("!daemonize() failed\n");
365 365
             localnets_free();
366 366
             whitelist_free();
... ...
@@ -425,6 +433,22 @@ int main(int argc, char **argv)
425 425
         }
426 426
         umask(old_umask);
427 427
 
428
+#ifndef _WIN32
429
+        if (0 == err){
430
+            /*If the file has already been created by a different user, it will just be
431
+             * rewritten by us, but not change the ownership, so do that explicitly.
432
+             */
433
+            if (0 == geteuid()){
434
+                struct passwd * pw = getpwuid(0);
435
+                int ret = lchown(opt->strarg, pw->pw_uid, pw->pw_gid);
436
+                if (ret){
437
+                    logg("!Can't change ownership of PID file %s '%s'\n", opt->strarg, strerror(errno));
438
+                    err = 1;
439
+                }
440
+            }
441
+        }
442
+#endif /*_WIN32*/
443
+
428 444
         if (err){
429 445
             localnets_free();
430 446
             whitelist_free();
... ...
@@ -434,42 +458,13 @@ int main(int argc, char **argv)
434 434
         }
435 435
     }
436 436
 
437
-
438
-    if (geteuid() == 0 && (opt = optget(opts, "User"))->enabled) {
439
-        struct passwd *user = NULL;
440
-        if ((user = getpwnam(opt->strarg)) == NULL) {
441
-            fprintf(stderr, "ERROR: Can't get information about user %s.\n", opt->strarg);
442
-            optfree(opts);
443
-            return 1;
444
-        }
445
-
446
-#ifdef HAVE_INITGROUPS
447
-        if (initgroups(opt->strarg, user->pw_gid)) {
448
-            fprintf(stderr, "ERROR: initgroups() failed.\n");
449
-            optfree(opts);
450
-            return 1;
451
-        }
452
-#elif HAVE_SETGROUPS
453
-        if (setgroups(1, &user->pw_gid)) {
454
-            fprintf(stderr, "ERROR: setgroups() failed.\n");
455
-            optfree(opts);
456
-            return 1;
457
-        }
458
-#endif
459
-        if (setgid(user->pw_gid)) {
460
-            fprintf(stderr, "ERROR: setgid(%d) failed.\n", (int)user->pw_gid);
461
-            optfree(opts);
462
-            return 1;
463
-        }
464
-
465
-        if (setuid(user->pw_uid)) {
466
-            fprintf(stderr, "ERROR: setuid(%d) failed.\n", (int)user->pw_uid);
467
-            optfree(opts);
468
-            return 1;
469
-        }
437
+#ifndef _WIN32
438
+    dropPrivRet = drop_privileges(user_name, logg_file);
439
+    if (dropPrivRet){
440
+        optfree(opts);
441
+        return dropPrivRet;
470 442
     }
471 443
 
472
-#ifndef _WIN32
473 444
     /* We have been daemonized, and initialization is done.  Signal
474 445
      * the parent process so that it can exit cleanly.
475 446
      */
... ...
@@ -126,6 +126,7 @@ int main(int argc, char **argv)
126 126
     struct passwd *user = NULL;
127 127
     struct sigaction sa;
128 128
     struct rlimit rlim;
129
+    int dropPrivRet = 0;
129 130
 #endif
130 131
     time_t currtime;
131 132
     const char *dbdir, *cfgfile;
... ...
@@ -144,6 +145,7 @@ int main(int argc, char **argv)
144 144
 #endif
145 145
     pid_t mainpid = 0;
146 146
     mode_t old_umask = 0;
147
+    const char * user_name = NULL;
147 148
 
148 149
     if (check_flevel())
149 150
         exit(1);
... ...
@@ -208,6 +210,10 @@ int main(int argc, char **argv)
208 208
     }
209 209
     free(pt);
210 210
 
211
+    if ((opt = optget(opts, "User"))->enabled){
212
+        user_name = opt->strarg;
213
+    }
214
+
211 215
     if (optget(opts, "version")->enabled) {
212 216
         print_version(optget(opts, "DatabaseDirectory")->strarg);
213 217
         optfree(opts);
... ...
@@ -267,7 +273,7 @@ int main(int argc, char **argv)
267 267
 #endif
268 268
             gengine = engine;
269 269
             atexit(free_engine);
270
-            daemonizeRet = daemonize_parent_wait();
270
+            daemonizeRet = daemonize_parent_wait(user_name, logg_file);
271 271
             if (daemonizeRet < 0){
272 272
                 logg("!daemonize() failed: %s\n", strerror(errno));
273 273
                 return 1;
... ...
@@ -306,45 +312,30 @@ int main(int argc, char **argv)
306 306
             fclose(fd);
307 307
         }
308 308
         umask(old_umask);
309
-    }
310 309
 
311
-    /* drop privileges */
312 310
 #ifndef _WIN32
313
-    if (geteuid() == 0 && (opt = optget(opts, "User"))->enabled) {
314
-        if ((user = getpwnam(opt->strarg)) == NULL) {
315
-            fprintf(stderr, "ERROR: Can't get information about user %s.\n", opt->strarg);
316
-            optfree(opts);
317
-            return 1;
318
-        }
319
-
320
-#ifdef HAVE_INITGROUPS
321
-        if (initgroups(opt->strarg, user->pw_gid)) {
322
-            fprintf(stderr, "ERROR: initgroups() failed.\n");
323
-            optfree(opts);
324
-            return 1;
325
-        }
326
-#elif HAVE_SETGROUPS
327
-        if (setgroups(1, &user->pw_gid)) {
328
-            fprintf(stderr, "ERROR: setgroups() failed.\n");
329
-            optfree(opts);
330
-            return 1;
331
-        }
332
-#endif
333
-
334
-        if (setgid(user->pw_gid)) {
335
-            fprintf(stderr, "ERROR: setgid(%d) failed.\n", (int)user->pw_gid);
336
-            optfree(opts);
337
-            return 1;
338
-        }
339
-
340
-        if (setuid(user->pw_uid)) {
341
-            fprintf(stderr, "ERROR: setuid(%d) failed.\n", (int)user->pw_uid);
342
-            optfree(opts);
343
-            return 1;
311
+        /*If the file has already been created by a different user, it will just be
312
+         * rewritten by us, but not change the ownership, so do that explicitly.
313
+         */
314
+        if (0 == geteuid()){
315
+            struct passwd * pw = getpwuid(0);
316
+            int ret = lchown(opt->strarg, pw->pw_uid, pw->pw_gid);
317
+            if (ret){
318
+                logg("!Can't change ownership of PID file %s '%s'\n", opt->strarg, strerror(errno));
319
+                exit(2);
320
+            }
344 321
         }
322
+#endif /* _WIN32 */
345 323
     }
346
-#endif
347 324
 
325
+    /* drop privileges */
326
+#ifndef _WIN32
327
+    dropPrivRet = drop_privileges(user_name, logg_file);
328
+    if (dropPrivRet) {
329
+        optfree(opts);
330
+        return dropPrivRet;
331
+    }
332
+#endif /* _WIN32 */
348 333
 
349 334
     do { /* logger initialized */
350 335
 
... ...
@@ -59,6 +59,9 @@ Example
59 59
 
60 60
 # This option allows you to save a process identifier of the listening
61 61
 # daemon (main thread).
62
+# This file will be owned by root, as long as clamav-milter was started by 
63
+# root.  It is recommended that the directory where this file is stored is
64
+# also owned by root to keep other users from tampering with it.
62 65
 #
63 66
 # Default: disabled
64 67
 #PidFile /var/run/clamav-milter.pid
... ...
@@ -70,6 +70,9 @@ Example
70 70
 
71 71
 # This option allows you to save a process identifier of the listening
72 72
 # daemon (main thread).
73
+# This file will be owned by root, as long as clamd was started by root.
74
+# It is recommended that the directory where this file is stored is
75
+# also owned by root to keep other users from tampering with it.
73 76
 # Default: disabled
74 77
 #PidFile /var/run/clamd.pid
75 78
 
... ...
@@ -47,6 +47,9 @@ Example
47 47
 #LogRotate yes
48 48
 
49 49
 # This option allows you to save the process identifier of the daemon
50
+# This file will be owned by root, as long as freshclam was started by root.
51
+# It is recommended that the directory where this file is stored is
52
+# also owned by root to keep other users from tampering with it.
50 53
 # Default: disabled
51 54
 #PidFile /var/run/freshclam.pid
52 55
 
... ...
@@ -135,6 +135,21 @@ static int writepid(const char *pidfile)
135 135
         fclose(fd);
136 136
     }
137 137
     umask(old_umask);
138
+
139
+#ifndef _WIN32
140
+    /*If the file has already been created by a different user, it will just be
141
+     * rewritten by us, but not change the ownership, so do that explicitly.
142
+     */
143
+    if (0 == geteuid()){
144
+        struct passwd * pw = getpwuid(0);
145
+        int ret = lchown(pidfile, pw->pw_uid, pw->pw_gid);
146
+        if (ret){
147
+            logg("!Can't change ownership of PID file %s '%s'\n", pidfile, strerror(errno));
148
+            return 1;
149
+        }
150
+    }
151
+#endif /*_WIN32 */
152
+
138 153
     return 0;
139 154
 }
140 155
 
... ...
@@ -629,68 +644,6 @@ done:
629 629
 }
630 630
 
631 631
 /**
632
- * @brief Switch users to the DatabaseOwner, if specified.
633
- *
634
- * @param dbowner     A user account that will have write permissions in the database directory.
635
- * @return fc_error_t FC_SUCCESS if success.
636
- * @return fc_error_t FC_EARG if success.
637
- */
638
-static fc_error_t switch_user(const char *dbowner)
639
-{
640
-    fc_error_t status = FC_EARG;
641
-
642
-#ifdef HAVE_PWD_H
643
-    struct passwd *user;
644
-
645
-    if (NULL == dbowner) {
646
-        logg("*No DatabaseOwner specified. Freshclam will run as the current user.\n");
647
-        status = FC_SUCCESS;
648
-        goto done;
649
-    }
650
-
651
-    if (!geteuid()) {
652
-        if ((user = getpwnam(dbowner)) == NULL) {
653
-            logg("^Can't get information about user %s.\n", dbowner);
654
-            status = FC_ECONFIG;
655
-            goto done;
656
-        }
657
-
658
-#ifdef HAVE_INITGROUPS
659
-        if (initgroups(dbowner, user->pw_gid)) {
660
-            logg("^initgroups() failed.\n");
661
-            status = FC_ECONFIG;
662
-            goto done;
663
-        }
664
-#elif HAVE_SETGROUPS
665
-        if (setgroups(1, &user->pw_gid)) {
666
-            logg("^setgroups() failed.\n");
667
-            status = FC_ECONFIG;
668
-            goto done;
669
-        }
670
-#endif
671
-
672
-        if (setgid(user->pw_gid)) {
673
-            logg("^setgid(%d) failed.\n", (int)user->pw_gid);
674
-            status = FC_ECONFIG;
675
-            goto done;
676
-        }
677
-
678
-        if (setuid(user->pw_uid)) {
679
-            logg("^setuid(%d) failed.\n", (int)user->pw_uid);
680
-            status = FC_ECONFIG;
681
-            goto done;
682
-        }
683
-    }
684
-#endif /* HAVE_PWD_H */
685
-
686
-    status = FC_SUCCESS;
687
-
688
-done:
689
-
690
-    return status;
691
-}
692
-
693
-/**
694 632
  * @brief Get a list of strings for a given repeatable opt argument.
695 633
  *
696 634
  * @param opt           optstruct of repeatable argument to collect in a list.
... ...
@@ -826,11 +779,12 @@ static fc_error_t initialize(struct optstruct *opts)
826 826
         /*
827 827
          * freshclam shouldn't work with root privileges.
828 828
          * Drop privileges to the DatabaseOwner user, if specified.
829
+         * Pass NULL for the log file name, because it hasn't been created yet.
829 830
          */
830
-        ret = switch_user(optget(opts, "DatabaseOwner")->strarg);
831
-        if (FC_SUCCESS != ret) {
831
+        ret = drop_privileges(optget(opts, "DatabaseOwner")->strarg, NULL);
832
+        if (ret) {
832 833
             logg("!Failed to switch to %s user.\n", optget(opts, "DatabaseOwner")->strarg);
833
-            status = ret;
834
+            status = FC_ECONFIG;
834 835
             goto done;
835 836
         }
836 837
     }
... ...
@@ -1580,6 +1534,11 @@ int main(int argc, char **argv)
1580 1580
 
1581 1581
     int bPrune = 1;
1582 1582
 
1583
+#ifdef HAVE_PWD_H
1584
+    const struct optstruct *logFileOpt = NULL;
1585
+    const char * logFileName = NULL;
1586
+#endif /* HAVE_PWD_H */
1587
+
1583 1588
     fc_ctx fc_context = {0};
1584 1589
 
1585 1590
 #ifndef _WIN32
... ...
@@ -1889,7 +1848,7 @@ int main(int argc, char **argv)
1889 1889
 #ifndef _WIN32
1890 1890
         /* fork into background */
1891 1891
         if (g_foreground == 0) {
1892
-            if (-1 == daemonize_parent_wait()) {
1892
+            if (-1 == daemonize_parent_wait(NULL, NULL)) {
1893 1893
                 logg("!daemonize() failed\n");
1894 1894
                 status = FC_EFAILEDUPDATE;
1895 1895
                 goto done;
... ...
@@ -1918,14 +1877,20 @@ int main(int argc, char **argv)
1918 1918
 #endif
1919 1919
 
1920 1920
 #ifdef HAVE_PWD_H
1921
+        /*  Get the log file name to pass it into drop_privileges.  */
1922
+        logFileOpt = optget(opts, "UpdateLogFile");
1923
+        if (logFileOpt->enabled) {
1924
+           logFileName  = logFileOpt->strarg;
1925
+        }
1926
+
1921 1927
         /*
1922 1928
          * freshclam shouldn't work with root privileges.
1923 1929
          * Drop privileges to the DatabaseOwner user, if specified.
1924 1930
          */
1925
-        ret = switch_user(optget(opts, "DatabaseOwner")->strarg);
1926
-        if (FC_SUCCESS != ret) {
1931
+        ret = drop_privileges(optget(opts, "DatabaseOwner")->strarg, logFileName);
1932
+        if (0 != ret) {
1927 1933
             logg("!Failed to switch to %s user.\n", optget(opts, "DatabaseOwner")->strarg);
1928
-            status = ret;
1934
+            status = FC_ECONFIG;
1929 1935
             goto done;
1930 1936
         }
1931 1937
 #endif /* HAVE_PWD_H */
... ...
@@ -34,6 +34,9 @@
34 34
 #include <sys/stat.h>
35 35
 #ifndef _WIN32
36 36
 #include <sys/socket.h>
37
+#include <pwd.h>
38
+#include <grp.h>
39
+#include <sys/types.h>
37 40
 #endif
38 41
 #include <dirent.h>
39 42
 #include <fcntl.h>
... ...
@@ -325,7 +328,7 @@ static void daemonize_child_initialized_handler(int sig)
325 325
     exit(0);
326 326
 }
327 327
 
328
-int daemonize_parent_wait()
328
+int daemonize_parent_wait(const char * const user, const char * const log_file)
329 329
 {
330 330
     int daemonizePid = daemonize_all_return();
331 331
     if (daemonizePid == -1) {
... ...
@@ -349,6 +352,12 @@ int daemonize_parent_wait()
349 349
             return -1;
350 350
         }
351 351
 
352
+        if (NULL != user){
353
+            if (drop_privileges(user, log_file)){
354
+                return -1;
355
+            }
356
+        }
357
+
352 358
         int exitStatus;
353 359
         wait(&exitStatus);
354 360
         if (WIFEXITED(exitStatus)) { //error
... ...
@@ -364,8 +373,68 @@ void daemonize_signal_parent(pid_t parentPid)
364 364
     close_std_descriptors();
365 365
     kill(parentPid, SIGINT);
366 366
 }
367
+
368
+int drop_privileges( const char * const user_name, const char * const log_file) {
369
+    int ret = 1;
370
+
371
+    /*This function is called in a bunch of places, and rather than change the error checking
372
+     * in every function, we are just going to return success if there is no work to do.
373
+     */
374
+    if ((0 == geteuid()) && (NULL != user_name)){
375
+        struct passwd *user = NULL;
376
+
377
+        if ((user = getpwnam(user_name)) == NULL) {
378
+            logg("^Can't get information about user %s.\n", user_name);
379
+            fprintf(stderr, "ERROR: Can't get information about user %s.\n", user_name);
380
+            goto done;
381
+        }
382
+
383
+#ifdef HAVE_INITGROUPS
384
+        if (initgroups(user_name, user->pw_gid)) {
385
+            fprintf(stderr, "ERROR: initgroups() failed.\n");
386
+            logg("^initgroups() failed.\n");
387
+            goto done;
388
+        }
389
+#elif HAVE_SETGROUPS
390
+        if (setgroups(1, &user->pw_gid)) {
391
+            fprintf(stderr, "ERROR: setgroups() failed.\n");
392
+            logg("^setgroups() failed.\n");
393
+            goto done;
394
+        }
367 395
 #endif
368 396
 
397
+        /*Change ownership of the log file to the user we are going to switch to.*/
398
+        if (NULL != log_file){
399
+            int ret = lchown(log_file, user->pw_uid, user->pw_gid);
400
+            if (ret){
401
+                fprintf(stderr, "ERROR: lchown to user '%s' failed on\n", user->pw_name);
402
+                fprintf(stderr, "log file '%s'.\n", log_file);
403
+                fprintf(stderr, "Error was '%s'\n", strerror(errno));
404
+                logg("^lchown to user '%s' failed on log file '%s'.  Error was '%s'\n", 
405
+                        user->pw_name, log_file, strerror(errno));
406
+                goto done;
407
+            }
408
+        }
409
+
410
+        if (setgid(user->pw_gid)) {
411
+            fprintf(stderr, "ERROR: setgid(%d) failed.\n", (int)user->pw_gid);
412
+            logg("^setgid(%d) failed.\n", (int)user->pw_gid);
413
+            goto done;
414
+        }
415
+
416
+        if (setuid(user->pw_uid)) {
417
+            fprintf(stderr, "ERROR: setuid(%d) failed.\n", (int)user->pw_uid);
418
+            logg("^setuid(%d) failed.\n", (int)user->pw_uid);
419
+            goto done;
420
+        }
421
+    }
422
+    ret = 0;
423
+
424
+done:
425
+    return ret;
426
+}
427
+#endif /*_WIN32*/
428
+
369 429
 int match_regex(const char *filename, const char *pattern)
370 430
 {
371 431
     regex_t reg;
... ...
@@ -26,6 +26,8 @@
26 26
 #include <netdb.h>
27 27
 #include <netinet/in.h>
28 28
 #endif
29
+#include <stdbool.h>
30
+
29 31
 #include "platform.h"
30 32
 #include "optparser.h"
31 33
 /* Maximum filenames under various systems - njh */
... ...
@@ -77,13 +79,26 @@ int daemonize_all_return(void);
77 77
 
78 78
 /*Parent waits for a SIGINT or the child process to exit.  If
79 79
  * it receives a SIGINT, it exits with exit code 0.  If the child
80
- * exits (error), it exits with the child process's exit code.*/
81
-int daemonize_parent_wait();
80
+ * exits (error), it exits with the child process's exit code.
81
+ *
82
+ * @param user If user is supplied and this function is being called
83
+ * as root, daemonize_parent_wait will change the parent process
84
+ * to user before calling wait so that the child process can signal
85
+ * the parent when it is time to exit.  The child process will still
86
+ * return as root.
87
+ *
88
+ * @param log_file If user AND log_file are both supplied and this
89
+ * function is being called as root, the ownership of log_file will
90
+ * be changed to user.
91
+ */
92
+int daemonize_parent_wait(const char * const user, const char * const log_file);
82 93
 
83 94
 /*Sends a SIGINT to the parent process.  It also closes stdin, stdout, 
84 95
  * and stderr.*/
85 96
 void daemonize_signal_parent(pid_t parentPid);
86
-#endif
97
+
98
+int drop_privileges( const char * const user, const char * const log_file);
99
+#endif /* _WIN32 */
87 100
 
88 101
 const char *get_version(void);
89 102
 int match_regex(const char *filename, const char *pattern);