/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2015-05-23 20:18:34 UTC
  • mto: (237.7.304 trunk)
  • mto: This revision was merged to the branch mainline in revision 325.
  • Revision ID: teddy@recompile.se-20150523201834-e89ex4ito93yni8x
mandos: Use multiprocessing module to run checkers.

For a long time, the Mandos server has occasionally logged the message
"ERROR: Child process vanished".  This was never a fatal error, but it
has been annoying and slightly worrying, since a definite cause was
not found.  One potential cause could be the "multiprocessing" and
"subprocess" modules conflicting w.r.t. SIGCHLD.  To avoid this,
change the running of checkers from using subprocess.Popen
asynchronously to instead first create a multiprocessing.Process()
(which is asynchronous) calling a function, and have that function
then call subprocess.call() (which is synchronous).  In this way, the
only thing using any asynchronous subprocesses is the multiprocessing
module.

This makes it necessary to change one small thing in the D-Bus API,
since the subprocesses.call() function does not expose the raw wait(2)
status value.

DBUS-API (CheckerCompleted): Change the second value provided by this
                             D-Bus signal from the raw wait(2) status
                             to the actual terminating signal number.
mandos (subprocess_call_pipe): New function to be called by
                               multiprocessing.Process (starting a
                               separate process).
(Client.last_checker signal): New attribute for signal which
                              terminated last checker.  Like
                              last_checker_status, only not accessible
                              via D-Bus.
(Client.checker_callback): Take new "connection" argument and use it
                           to get returncode; set last_checker_signal.
                           Return False so gobject does not call this
                           callback again.
(Client.start_checker): Start checker using a multiprocessing.Process
                        instead of a subprocess.Popen.
(ClientDBus.checker_callback): Take new "connection" argument.        Call
                               Client.checker_callback early to have
                               it set last_checker_status and
                               last_checker_signal; use those.  Change
                               second value provided to D-Bus signal
                               CheckerCompleted to use
                               last_checker_signal if checker was
                               terminated by signal.
mandos-monitor: Update to reflect DBus API change.
(MandosClientWidget.checker_completed): Take "signal" instead of
                                        "condition" argument.  Use it
                                        accordingly.  Remove dead code
                                        (os.WCOREDUMP case).

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
 */
24
24
 
25
25
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
 
                                   O_CLOEXEC */
 
26
                                   O_CLOEXEC, pipe2() */
27
27
#include <stddef.h>             /* size_t, NULL */
28
28
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
29
                                   realloc() */
30
30
#include <stdbool.h>            /* bool, true, false */
31
31
#include <stdio.h>              /* fileno(), fprintf(),
32
32
                                   stderr, STDOUT_FILENO, fclose() */
33
 
#include <sys/types.h>          /* DIR, fdopendir(), fstat(), struct
34
 
                                   stat, waitpid(), WIFEXITED(),
35
 
                                   WEXITSTATUS(), wait(), pid_t,
36
 
                                   uid_t, gid_t, getuid(), getgid(),
37
 
                                   dirfd() */
 
33
#include <sys/types.h>          /* fstat(), struct stat, waitpid(),
 
34
                                   WIFEXITED(), WEXITSTATUS(), wait(),
 
35
                                   pid_t, uid_t, gid_t, getuid(),
 
36
                                   getgid() */
38
37
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
39
38
                                   FD_SET(), FD_ISSET(), FD_CLR */
40
39
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
42
41
                                   WCOREDUMP() */
43
42
#include <sys/stat.h>           /* struct stat, fstat(), S_ISREG() */
44
43
#include <iso646.h>             /* and, or, not */
45
 
#include <dirent.h>             /* DIR, struct dirent, fdopendir(),
46
 
                                   readdir(), closedir(), dirfd() */
 
44
#include <dirent.h>             /* struct dirent, scandirat() */
47
45
#include <unistd.h>             /* fcntl(), F_GETFD, F_SETFD,
48
46
                                   FD_CLOEXEC, write(), STDOUT_FILENO,
49
47
                                   struct stat, fstat(), close(),
50
48
                                   setgid(), setuid(), S_ISREG(),
51
 
                                   faccessat() pipe(), fork(),
 
49
                                   faccessat() pipe2(), fork(),
52
50
                                   _exit(), dup2(), fexecve(), read()
53
51
                                */
54
52
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
55
 
                                   FD_CLOEXEC, openat() */
 
53
                                   FD_CLOEXEC, openat(), scandirat(),
 
54
                                   pipe2() */
56
55
#include <string.h>             /* strsep, strlen(), strsignal(),
57
56
                                   strcmp(), strncmp() */
58
57
#include <errno.h>              /* errno */
241
240
  return add_to_char_array(def, &(p->environ), &(p->envc));
242
241
}
243
242
 
 
243
#ifndef O_CLOEXEC
244
244
/*
245
245
 * Based on the example in the GNU LibC manual chapter 13.13 "File
246
246
 * Descriptor Flags".
257
257
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
258
258
                                       ret | FD_CLOEXEC));
259
259
}
 
260
#endif  /* not O_CLOEXEC */
260
261
 
261
262
 
262
263
/* Mark processes as completed when they exit, and save their exit
348
349
  char *plugindir = NULL;
349
350
  char *argfile = NULL;
350
351
  FILE *conffp;
351
 
  DIR *dir = NULL;
352
 
  struct dirent *dirst;
 
352
  struct dirent **direntries = NULL;
353
353
  struct stat st;
354
354
  fd_set rfds_all;
355
355
  int ret, maxfd = 0;
363
363
                                      .sa_flags = SA_NOCLDSTOP };
364
364
  char **custom_argv = NULL;
365
365
  int custom_argc = 0;
366
 
  int dir_fd;
 
366
  int dir_fd = -1;
367
367
  
368
368
  /* Establish a signal handler */
369
369
  sigemptyset(&sigchld_action.sa_mask);
829
829
    ret = set_cloexec_flag(dir_fd);
830
830
    if(ret < 0){
831
831
      error(0, errno, "set_cloexec_flag");
832
 
      TEMP_FAILURE_RETRY(close(dir_fd));
833
832
      exitstatus = EX_OSERR;
834
833
      goto fallback;
835
834
    }
836
835
#endif  /* O_CLOEXEC */
837
 
    
838
 
    dir = fdopendir(dir_fd);
839
 
    if(dir == NULL){
840
 
      error(0, errno, "Could not open plugin dir");
841
 
      TEMP_FAILURE_RETRY(close(dir_fd));
842
 
      exitstatus = EX_OSERR;
843
 
      goto fallback;
 
836
  }
 
837
  
 
838
  int good_name(const struct dirent * const dirent){
 
839
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
840
                                      "*.dpkg-old", "*.dpkg-bak",
 
841
                                      "*.dpkg-divert", NULL };
 
842
#ifdef __GNUC__
 
843
#pragma GCC diagnostic push
 
844
#pragma GCC diagnostic ignored "-Wcast-qual"
 
845
#endif
 
846
    for(const char **pat = (const char **)patterns;
 
847
        *pat != NULL; pat++){
 
848
#ifdef __GNUC__
 
849
#pragma GCC diagnostic pop
 
850
#endif
 
851
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
852
         != FNM_NOMATCH){
 
853
        if(debug){
 
854
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
855
                    " matching pattern %s\n", dirent->d_name, *pat);
 
856
        }
 
857
        return 0;
 
858
      }
844
859
    }
 
860
    return 1;
 
861
  }
 
862
  
 
863
#ifdef __GLIBC__
 
864
#if __GLIBC_PREREQ(2, 15)
 
865
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
866
                             alphasort);
 
867
#else  /* not __GLIBC_PREREQ(2, 15) */
 
868
  int numplugins = scandir(plugindir != NULL ? plugindir : PDIR,
 
869
                           &direntries, good_name, alphasort);
 
870
#endif  /* not __GLIBC_PREREQ(2, 15) */
 
871
#else   /* not __GLIBC__ */
 
872
  int numplugins = scandir(plugindir != NULL ? plugindir : PDIR,
 
873
                           &direntries, good_name, alphasort);
 
874
#endif  /* not __GLIBC__ */
 
875
  if(numplugins == -1){
 
876
    error(0, errno, "Could not scan plugin dir");
 
877
    direntries = NULL;
 
878
    exitstatus = EX_OSERR;
 
879
    goto fallback;
845
880
  }
846
881
  
847
882
  FD_ZERO(&rfds_all);
848
883
  
849
884
  /* Read and execute any executable in the plugin directory*/
850
 
  while(true){
851
 
    do {
852
 
      dirst = readdir(dir);
853
 
    } while(dirst == NULL and errno == EINTR);
854
 
    
855
 
    /* All directory entries have been processed */
856
 
    if(dirst == NULL){
857
 
      if(errno == EBADF){
858
 
        error(0, errno, "readdir");
859
 
        exitstatus = EX_IOERR;
860
 
        goto fallback;
861
 
      }
862
 
      break;
863
 
    }
864
 
    
865
 
    /* Ignore dotfiles, backup files and other junk */
866
 
    {
867
 
      bool bad_name = false;
868
 
      const char * const patterns[] = { ".*", "#*#", "*~",
869
 
                                        "*.dpkg-new", "*.dpkg-old",
870
 
                                        "*.dpkg-bak", "*.dpkg-divert",
871
 
                                        NULL };
872
 
#ifdef __GNUC__
873
 
#pragma GCC diagnostic push
874
 
#pragma GCC diagnostic ignored "-Wcast-qual"
875
 
#endif
876
 
      for(const char **pat = (const char **)patterns;
877
 
          *pat != NULL; pat++){
878
 
#ifdef __GNUC__
879
 
#pragma GCC diagnostic pop
880
 
#endif
881
 
        if(fnmatch(*pat, dirst->d_name,
882
 
                   FNM_FILE_NAME | FNM_PERIOD) != FNM_NOMATCH){
883
 
          if(debug){
884
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
885
 
                    " matching pattern %s\n", dirst->d_name, *pat);
886
 
          }
887
 
          bad_name = true;
888
 
          break;
889
 
        }
890
 
      }
891
 
      if(bad_name){
892
 
        continue;
893
 
      }
894
 
    }
895
 
    
896
 
    int plugin_fd = openat(dir_fd, dirst->d_name, O_RDONLY |
897
 
#ifdef O_CLOEXEC
898
 
                            O_CLOEXEC
899
 
#else  /* not O_CLOEXEC */
900
 
                            0
901
 
#endif  /* not O_CLOEXEC */
902
 
                            );
 
885
  for(int i = 0; i < numplugins; i++){
 
886
    
 
887
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
903
888
    if(plugin_fd == -1){
904
889
      error(0, errno, "Could not open plugin");
905
 
      continue;
906
 
    }
907
 
#ifndef O_CLOEXEC
908
 
  /* Set the FD_CLOEXEC flag on the plugin FD */
909
 
    ret = set_cloexec_flag(plugin_fd);
910
 
    if(ret < 0){
911
 
      error(0, errno, "set_cloexec_flag");
912
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
913
 
      continue;
914
 
    }
915
 
#endif  /* O_CLOEXEC */
 
890
      free(direntries[i]);
 
891
      continue;
 
892
    }
916
893
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
917
894
    if(ret == -1){
918
895
      error(0, errno, "stat");
919
896
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
897
      free(direntries[i]);
920
898
      continue;
921
899
    }
922
900
    
923
901
    /* Ignore non-executable files */
924
902
    if(not S_ISREG(st.st_mode)
925
 
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, dirst->d_name, X_OK,
926
 
                                        0)) != 0)){
 
903
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
904
                                        X_OK, 0)) != 0)){
927
905
      if(debug){
928
906
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
929
907
                " with bad type or mode\n",
930
 
                plugindir != NULL ? plugindir : PDIR, dirst->d_name);
 
908
                plugindir != NULL ? plugindir : PDIR,
 
909
                direntries[i]->d_name);
931
910
      }
932
911
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
912
      free(direntries[i]);
933
913
      continue;
934
914
    }
935
915
    
936
 
    plugin *p = getplugin(dirst->d_name);
 
916
    plugin *p = getplugin(direntries[i]->d_name);
937
917
    if(p == NULL){
938
918
      error(0, errno, "getplugin");
939
919
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
920
      free(direntries[i]);
940
921
      continue;
941
922
    }
942
923
    if(p->disabled){
943
924
      if(debug){
944
925
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
945
 
                dirst->d_name);
 
926
                direntries[i]->d_name);
946
927
      }
947
928
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
929
      free(direntries[i]);
948
930
      continue;
949
931
    }
950
932
    {
975
957
    }
976
958
    
977
959
    int pipefd[2];
 
960
#ifndef O_CLOEXEC
978
961
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
962
#else  /* O_CLOEXEC */
 
963
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
964
#endif  /* O_CLOEXEC */
979
965
    if(ret == -1){
980
966
      error(0, errno, "pipe");
981
967
      exitstatus = EX_OSERR;
982
 
      goto fallback;
983
 
    }
 
968
      free(direntries[i]);
 
969
      goto fallback;
 
970
    }
 
971
    if(pipefd[0] >= FD_SETSIZE){
 
972
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
 
973
              FD_SETSIZE);
 
974
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
975
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
976
      exitstatus = EX_OSERR;
 
977
      free(direntries[i]);
 
978
      goto fallback;
 
979
    }
 
980
#ifndef O_CLOEXEC
984
981
    /* Ask OS to automatic close the pipe on exec */
985
982
    ret = set_cloexec_flag(pipefd[0]);
986
983
    if(ret < 0){
987
984
      error(0, errno, "set_cloexec_flag");
 
985
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
986
      TEMP_FAILURE_RETRY(close(pipefd[1]));
988
987
      exitstatus = EX_OSERR;
 
988
      free(direntries[i]);
989
989
      goto fallback;
990
990
    }
991
991
    ret = set_cloexec_flag(pipefd[1]);
992
992
    if(ret < 0){
993
993
      error(0, errno, "set_cloexec_flag");
 
994
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
995
      TEMP_FAILURE_RETRY(close(pipefd[1]));
994
996
      exitstatus = EX_OSERR;
 
997
      free(direntries[i]);
995
998
      goto fallback;
996
999
    }
 
1000
#endif  /* not O_CLOEXEC */
997
1001
    /* Block SIGCHLD until process is safely in process list */
998
1002
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
999
1003
                                              &sigchld_action.sa_mask,
1001
1005
    if(ret < 0){
1002
1006
      error(0, errno, "sigprocmask");
1003
1007
      exitstatus = EX_OSERR;
 
1008
      free(direntries[i]);
1004
1009
      goto fallback;
1005
1010
    }
1006
1011
    /* Starting a new process to be watched */
1010
1015
    } while(pid == -1 and errno == EINTR);
1011
1016
    if(pid == -1){
1012
1017
      error(0, errno, "fork");
 
1018
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1019
                                     &sigchld_action.sa_mask, NULL));
 
1020
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
1021
      TEMP_FAILURE_RETRY(close(pipefd[1]));
1013
1022
      exitstatus = EX_OSERR;
 
1023
      free(direntries[i]);
1014
1024
      goto fallback;
1015
1025
    }
1016
1026
    if(pid == 0){
1032
1042
        _exit(EX_OSERR);
1033
1043
      }
1034
1044
      
1035
 
      if(dirfd(dir) < 0){
1036
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
1037
 
           above and must now close it manually here. */
1038
 
        closedir(dir);
1039
 
      }
1040
1045
      if(fexecve(plugin_fd, p->argv,
1041
1046
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
1042
1047
        error(0, errno, "fexecve for %s/%s",
1043
 
              plugindir != NULL ? plugindir : PDIR, dirst->d_name);
 
1048
              plugindir != NULL ? plugindir : PDIR,
 
1049
              direntries[i]->d_name);
1044
1050
        _exit(EX_OSERR);
1045
1051
      }
1046
1052
      /* no return */
1049
1055
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1050
1056
                                             pipe */
1051
1057
    TEMP_FAILURE_RETRY(close(plugin_fd));
1052
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1058
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1053
1059
    if(new_plugin == NULL){
1054
1060
      error(0, errno, "getplugin");
1055
1061
      ret = (int)(TEMP_FAILURE_RETRY
1059
1065
        error(0, errno, "sigprocmask");
1060
1066
      }
1061
1067
      exitstatus = EX_OSERR;
 
1068
      free(direntries[i]);
1062
1069
      goto fallback;
1063
1070
    }
 
1071
    free(direntries[i]);
1064
1072
    
1065
1073
    new_plugin->pid = pid;
1066
1074
    new_plugin->fd = pipefd[0];
1096
1104
    }
1097
1105
  }
1098
1106
  
1099
 
  TEMP_FAILURE_RETRY(closedir(dir));
1100
 
  dir = NULL;
 
1107
  free(direntries);
 
1108
  direntries = NULL;
 
1109
  TEMP_FAILURE_RETRY(close(dir_fd));
 
1110
  dir_fd = -1;
1101
1111
  free_plugin(getplugin(NULL));
1102
1112
  
1103
1113
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1297
1307
    free(custom_argv);
1298
1308
  }
1299
1309
  
1300
 
  if(dir != NULL){
1301
 
    closedir(dir);
 
1310
  free(direntries);
 
1311
  
 
1312
  if(dir_fd != -1){
 
1313
    TEMP_FAILURE_RETRY(close(dir_fd));
1302
1314
  }
1303
1315
  
1304
1316
  /* Kill the processes */