/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: teddy at recompile
  • Date: 2020-04-05 21:30:59 UTC
  • Revision ID: teddy@recompile.se-20200405213059-fb2a61ckqynrmatk
Fix file descriptor leak in mandos-client

When the local network has Mandos servers announcing themselves using
real, globally reachable, IPv6 addresses (i.e. not link-local
addresses), but there is no router on the local network providing IPv6
RA (Router Advertisement) packets, the client cannot reach the server
by normal means, since the client only has a link-local IPv6 address,
and has no usable route to reach the server's global IPv6 address.
(This is not a common situation, and usually only happens when the
router itself reboots and runs a Mandos client, since it cannot then
give RA packets to itself.)  The client code has a solution for
this, which consists of adding a temporary local route to reach the
address of the server during communication, and removing this
temporary route afterwards.

This solution with a temporary route works, but has a file descriptor
leak; it leaks one file descriptor for each addition and for each
removal of a route.  If one server requiring an added route is present
on the network, but no servers gives a password, making the client
retry after the default ten seconds, and we furthermore assume a
default 1024 open files limit, the client runs out of file descriptors
after about 90 minutes, after which time the client process will be
useless and fail to retrieve any passwords, necessitating manual
password entry via the keyboard.

Fix this by eliminating the file descriptor leak in the client.

* plugins.d/mandos-client.c (add_delete_local_route): Do
  close(devnull) also in parent process, also if fork() fails, and on
  any failure in child process.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2008-2014 Teddy Hogeborn
6
 
 * Copyright © 2008-2014 Björn Påhlsson
7
 
 * 
8
 
 * This program is free software: you can redistribute it and/or
9
 
 * modify it under the terms of the GNU General Public License as
10
 
 * published by the Free Software Foundation, either version 3 of the
11
 
 * License, or (at your option) any later version.
12
 
 * 
13
 
 * This program is distributed in the hope that it will be useful, but
 
5
 * Copyright © 2008-2018 Teddy Hogeborn
 
6
 * Copyright © 2008-2018 Björn Påhlsson
 
7
 * 
 
8
 * This file is part of Mandos.
 
9
 * 
 
10
 * Mandos is free software: you can redistribute it and/or modify it
 
11
 * under the terms of the GNU General Public License as published by
 
12
 * the Free Software Foundation, either version 3 of the License, or
 
13
 * (at your option) any later version.
 
14
 * 
 
15
 * Mandos is distributed in the hope that it will be useful, but
14
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
15
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
18
 * General Public License for more details.
17
19
 * 
18
20
 * You should have received a copy of the GNU General Public License
19
 
 * along with this program.  If not, see
20
 
 * <http://www.gnu.org/licenses/>.
 
21
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
21
22
 * 
22
23
 * Contact the authors at <mandos@recompile.se>.
23
24
 */
25
26
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
27
                                   O_CLOEXEC, pipe2() */
27
28
#include <stddef.h>             /* size_t, NULL */
28
 
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
 
                                   realloc() */
 
29
#include <stdlib.h>             /* malloc(), reallocarray(), realloc(),
 
30
                                   EXIT_SUCCESS, exit() */
30
31
#include <stdbool.h>            /* bool, true, false */
31
32
#include <stdio.h>              /* fileno(), fprintf(),
32
33
                                   stderr, STDOUT_FILENO, fclose() */
37
38
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
38
39
                                   FD_SET(), FD_ISSET(), FD_CLR */
39
40
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
40
 
                                   WEXITSTATUS(), WTERMSIG(),
41
 
                                   WCOREDUMP() */
 
41
                                   WEXITSTATUS(), WTERMSIG() */
42
42
#include <sys/stat.h>           /* struct stat, fstat(), S_ISREG() */
43
43
#include <iso646.h>             /* and, or, not */
44
44
#include <dirent.h>             /* struct dirent, scandirat() */
76
76
#define BUFFER_SIZE 256
77
77
 
78
78
#define PDIR "/lib/mandos/plugins.d"
 
79
#define PHDIR "/lib/mandos/plugin-helpers"
79
80
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
80
81
 
81
82
const char *argp_program_version = "plugin-runner " VERSION;
178
179
  /* Resize the pointed-to array to hold one more pointer */
179
180
  char **new_array = NULL;
180
181
  do {
181
 
    new_array = realloc(*array, sizeof(char *)
182
 
                        * (size_t) ((*len) + 2));
 
182
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
 
183
    new_array = reallocarray(*array, (size_t)((*len) + 2),
 
184
                             sizeof(char *));
 
185
#else
 
186
    if(((size_t)((*len) + 2)) > (SIZE_MAX / sizeof(char *))){
 
187
      /* overflow */
 
188
      new_array = NULL;
 
189
      errno = ENOMEM;
 
190
    } else {
 
191
      new_array = realloc(*array, (size_t)((*len) + 2)
 
192
                          * sizeof(char *));
 
193
    }
 
194
#endif
183
195
  } while(new_array == NULL and errno == EINTR);
184
196
  /* Malloc check */
185
197
  if(new_array == NULL){
312
324
__attribute__((nonnull))
313
325
static void free_plugin(plugin *plugin_node){
314
326
  
315
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
327
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
316
328
    free(*arg);
317
329
  }
 
330
  free(plugin_node->name);
318
331
  free(plugin_node->argv);
319
332
  for(char **env = plugin_node->environ; *env != NULL; env++){
320
333
    free(*env);
347
360
 
348
361
int main(int argc, char *argv[]){
349
362
  char *plugindir = NULL;
 
363
  char *pluginhelperdir = NULL;
350
364
  char *argfile = NULL;
351
365
  FILE *conffp;
352
366
  struct dirent **direntries = NULL;
414
428
      .doc = "Group ID the plugins will run as", .group = 3 },
415
429
    { .name = "debug", .key = 132,
416
430
      .doc = "Debug mode", .group = 4 },
 
431
    { .name = "plugin-helper-dir", .key = 133,
 
432
      .arg = "DIRECTORY",
 
433
      .doc = "Specify a different plugin helper directory",
 
434
      .group = 2 },
417
435
    /*
418
436
     * These reproduce what we would get without ARGP_NO_HELP
419
437
     */
545
563
    case 132:                   /* --debug */
546
564
      debug = true;
547
565
      break;
 
566
    case 133:                   /* --plugin-helper-dir */
 
567
      free(pluginhelperdir);
 
568
      pluginhelperdir = strdup(arg);
 
569
      if(pluginhelperdir != NULL){
 
570
        errno = 0;
 
571
      }
 
572
      break;
548
573
      /*
549
574
       * These reproduce what we would get without ARGP_NO_HELP
550
575
       */
551
576
    case '?':                   /* --help */
552
577
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
553
578
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
579
      __builtin_unreachable();
554
580
    case -3:                    /* --usage */
555
581
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
556
582
      argp_state_help(state, state->out_stream,
557
583
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
584
      __builtin_unreachable();
558
585
    case 'V':                   /* --version */
559
586
      fprintf(state->out_stream, "%s\n", argp_program_version);
560
587
      exit(EXIT_SUCCESS);
570
597
      if(arg[0] == '\0'){
571
598
        break;
572
599
      }
 
600
#if __GNUC__ >= 7
 
601
      __attribute__((fallthrough));
 
602
#else
 
603
          /* FALLTHROUGH */
 
604
#endif
573
605
    default:
574
606
      return ARGP_ERR_UNKNOWN;
575
607
    }
601
633
    case 130:                   /* --userid */
602
634
    case 131:                   /* --groupid */
603
635
    case 132:                   /* --debug */
 
636
    case 133:                   /* --plugin-helper-dir */
604
637
    case '?':                   /* --help */
605
638
    case -3:                    /* --usage */
606
639
    case 'V':                   /* --version */
686
719
        
687
720
        custom_argc += 1;
688
721
        {
689
 
          char **new_argv = realloc(custom_argv, sizeof(char *)
690
 
                                    * ((unsigned int)
691
 
                                       custom_argc + 1));
 
722
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
 
723
          char **new_argv = reallocarray(custom_argv, (size_t)custom_argc + 1,
 
724
                                         sizeof(char *));
 
725
#else
 
726
          char **new_argv = NULL;
 
727
          if(((size_t)custom_argc + 1) > (SIZE_MAX / sizeof(char *))){
 
728
            /* overflow */
 
729
            errno = ENOMEM;
 
730
          } else {
 
731
            new_argv = realloc(custom_argv, ((size_t)custom_argc + 1)
 
732
                               * sizeof(char *));
 
733
          }
 
734
#endif
692
735
          if(new_argv == NULL){
693
 
            error(0, errno, "realloc");
 
736
            error(0, errno, "reallocarray");
694
737
            exitstatus = EX_OSERR;
695
738
            free(new_arg);
696
739
            free(org_line);
761
804
    goto fallback;
762
805
  }
763
806
  
 
807
  {
 
808
    char *pluginhelperenv;
 
809
    bool bret = true;
 
810
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
 
811
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
812
    if(ret != -1){
 
813
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
 
814
    }
 
815
    if(ret == -1 or not bret){
 
816
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
 
817
            " environment variable to \"%s\" for all plugins\n",
 
818
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
819
    }
 
820
    if(ret != -1){
 
821
      free(pluginhelperenv);
 
822
    }
 
823
  }
 
824
  
764
825
  if(debug){
765
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
826
    for(plugin *p = plugin_list; p != NULL; p = p->next){
766
827
      fprintf(stderr, "Plugin: %s has %d arguments\n",
767
828
              p->name ? p->name : "Global", p->argc - 1);
768
829
      for(char **a = p->argv; *a != NULL; a++){
777
838
  
778
839
  if(getuid() == 0){
779
840
    /* Work around Debian bug #633582:
780
 
       <http://bugs.debian.org/633582> */
 
841
       <https://bugs.debian.org/633582> */
781
842
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
782
843
    if(plugindir_fd == -1){
783
844
      if(errno != ENOENT){
795
856
          }
796
857
        }
797
858
      }
798
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
859
      close(plugindir_fd);
799
860
    }
800
861
  }
801
862
  
860
921
    return 1;
861
922
  }
862
923
  
863
 
#ifdef __GLIBC__
864
 
#if __GLIBC_PREREQ(2, 15)
865
924
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
866
925
                             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
926
  if(numplugins == -1){
876
927
    error(0, errno, "Could not scan plugin dir");
877
928
    direntries = NULL;
893
944
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
894
945
    if(ret == -1){
895
946
      error(0, errno, "stat");
896
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
947
      close(plugin_fd);
897
948
      free(direntries[i]);
898
949
      continue;
899
950
    }
908
959
                plugindir != NULL ? plugindir : PDIR,
909
960
                direntries[i]->d_name);
910
961
      }
911
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
962
      close(plugin_fd);
912
963
      free(direntries[i]);
913
964
      continue;
914
965
    }
916
967
    plugin *p = getplugin(direntries[i]->d_name);
917
968
    if(p == NULL){
918
969
      error(0, errno, "getplugin");
919
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
970
      close(plugin_fd);
920
971
      free(direntries[i]);
921
972
      continue;
922
973
    }
925
976
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
926
977
                direntries[i]->d_name);
927
978
      }
928
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
979
      close(plugin_fd);
929
980
      free(direntries[i]);
930
981
      continue;
931
982
    }
971
1022
    if(pipefd[0] >= FD_SETSIZE){
972
1023
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
973
1024
              FD_SETSIZE);
974
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
975
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1025
      close(pipefd[0]);
 
1026
      close(pipefd[1]);
976
1027
      exitstatus = EX_OSERR;
977
1028
      free(direntries[i]);
978
1029
      goto fallback;
982
1033
    ret = set_cloexec_flag(pipefd[0]);
983
1034
    if(ret < 0){
984
1035
      error(0, errno, "set_cloexec_flag");
985
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
986
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1036
      close(pipefd[0]);
 
1037
      close(pipefd[1]);
987
1038
      exitstatus = EX_OSERR;
988
1039
      free(direntries[i]);
989
1040
      goto fallback;
991
1042
    ret = set_cloexec_flag(pipefd[1]);
992
1043
    if(ret < 0){
993
1044
      error(0, errno, "set_cloexec_flag");
994
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
995
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
996
1047
      exitstatus = EX_OSERR;
997
1048
      free(direntries[i]);
998
1049
      goto fallback;
1017
1068
      error(0, errno, "fork");
1018
1069
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1019
1070
                                     &sigchld_action.sa_mask, NULL));
1020
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
1021
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1071
      close(pipefd[0]);
 
1072
      close(pipefd[1]);
1022
1073
      exitstatus = EX_OSERR;
1023
1074
      free(direntries[i]);
1024
1075
      goto fallback;
1052
1103
      /* no return */
1053
1104
    }
1054
1105
    /* Parent process */
1055
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1056
 
                                             pipe */
1057
 
    TEMP_FAILURE_RETRY(close(plugin_fd));
 
1106
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1107
    close(plugin_fd);
1058
1108
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1059
1109
    if(new_plugin == NULL){
1060
1110
      error(0, errno, "getplugin");
1072
1122
    
1073
1123
    new_plugin->pid = pid;
1074
1124
    new_plugin->fd = pipefd[0];
1075
 
    
 
1125
 
 
1126
    if(debug){
 
1127
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1128
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1129
    }
 
1130
 
1076
1131
    /* Unblock SIGCHLD so signal handler can be run if this process
1077
1132
       has already completed */
1078
1133
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1084
1139
      goto fallback;
1085
1140
    }
1086
1141
    
1087
 
#if defined (__GNUC__) and defined (__GLIBC__)
1088
 
#if not __GLIBC_PREREQ(2, 16)
1089
 
#pragma GCC diagnostic push
1090
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1091
 
#endif
1092
 
#endif
1093
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1094
 
                                          -Wconversion in GNU libc
1095
 
                                          before 2.16 */
1096
 
#if defined (__GNUC__) and defined (__GLIBC__)
1097
 
#if not __GLIBC_PREREQ(2, 16)
1098
 
#pragma GCC diagnostic pop
1099
 
#endif
1100
 
#endif
 
1142
    FD_SET(new_plugin->fd, &rfds_all);
1101
1143
    
1102
1144
    if(maxfd < new_plugin->fd){
1103
1145
      maxfd = new_plugin->fd;
1106
1148
  
1107
1149
  free(direntries);
1108
1150
  direntries = NULL;
1109
 
  TEMP_FAILURE_RETRY(close(dir_fd));
 
1151
  close(dir_fd);
1110
1152
  dir_fd = -1;
1111
1153
  free_plugin(getplugin(NULL));
1112
1154
  
1152
1194
                      (intmax_t) (proc->pid),
1153
1195
                      WTERMSIG(proc->status),
1154
1196
                      strsignal(WTERMSIG(proc->status)));
1155
 
            } else if(WCOREDUMP(proc->status)){
1156
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1157
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1158
1197
            }
1159
1198
          }
1160
1199
          
1161
1200
          /* Remove the plugin */
1162
 
#if defined (__GNUC__) and defined (__GLIBC__)
1163
 
#if not __GLIBC_PREREQ(2, 16)
1164
 
#pragma GCC diagnostic push
1165
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1166
 
#endif
1167
 
#endif
1168
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1169
 
                                          -Wconversion in GNU libc
1170
 
                                          before 2.16 */
1171
 
#if defined (__GNUC__) and defined (__GLIBC__)
1172
 
#if not __GLIBC_PREREQ(2, 16)
1173
 
#pragma GCC diagnostic pop
1174
 
#endif
1175
 
#endif
 
1201
          FD_CLR(proc->fd, &rfds_all);
1176
1202
          
1177
1203
          /* Block signal while modifying process_list */
1178
1204
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1218
1244
      }
1219
1245
      
1220
1246
      /* This process has not completed.  Does it have any output? */
1221
 
#if defined (__GNUC__) and defined (__GLIBC__)
1222
 
#if not __GLIBC_PREREQ(2, 16)
1223
 
#pragma GCC diagnostic push
1224
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1225
 
#endif
1226
 
#endif
1227
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1228
 
                                                         warning from
1229
 
                                                         -Wconversion
1230
 
                                                         in GNU libc
1231
 
                                                         before
1232
 
                                                         2.16 */
1233
 
#if defined (__GNUC__) and defined (__GLIBC__)
1234
 
#if not __GLIBC_PREREQ(2, 16)
1235
 
#pragma GCC diagnostic pop
1236
 
#endif
1237
 
#endif
 
1247
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1238
1248
        /* This process had nothing to say at this time */
1239
1249
        proc = proc->next;
1240
1250
        continue;
1310
1320
  free(direntries);
1311
1321
  
1312
1322
  if(dir_fd != -1){
1313
 
    TEMP_FAILURE_RETRY(close(dir_fd));
 
1323
    close(dir_fd);
1314
1324
  }
1315
1325
  
1316
1326
  /* Kill the processes */
1336
1346
  free_plugin_list();
1337
1347
  
1338
1348
  free(plugindir);
 
1349
  free(pluginhelperdir);
1339
1350
  free(argfile);
1340
1351
  
1341
1352
  return exitstatus;