/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() */
179
179
  /* Resize the pointed-to array to hold one more pointer */
180
180
  char **new_array = NULL;
181
181
  do {
182
 
    new_array = realloc(*array, sizeof(char *)
183
 
                        * (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
184
195
  } while(new_array == NULL and errno == EINTR);
185
196
  /* Malloc check */
186
197
  if(new_array == NULL){
313
324
__attribute__((nonnull))
314
325
static void free_plugin(plugin *plugin_node){
315
326
  
316
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
327
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
317
328
    free(*arg);
318
329
  }
 
330
  free(plugin_node->name);
319
331
  free(plugin_node->argv);
320
332
  for(char **env = plugin_node->environ; *env != NULL; env++){
321
333
    free(*env);
564
576
    case '?':                   /* --help */
565
577
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
566
578
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
579
      __builtin_unreachable();
567
580
    case -3:                    /* --usage */
568
581
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
569
582
      argp_state_help(state, state->out_stream,
570
583
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
584
      __builtin_unreachable();
571
585
    case 'V':                   /* --version */
572
586
      fprintf(state->out_stream, "%s\n", argp_program_version);
573
587
      exit(EXIT_SUCCESS);
583
597
      if(arg[0] == '\0'){
584
598
        break;
585
599
      }
 
600
#if __GNUC__ >= 7
 
601
      __attribute__((fallthrough));
 
602
#else
 
603
          /* FALLTHROUGH */
 
604
#endif
586
605
    default:
587
606
      return ARGP_ERR_UNKNOWN;
588
607
    }
700
719
        
701
720
        custom_argc += 1;
702
721
        {
703
 
          char **new_argv = realloc(custom_argv, sizeof(char *)
704
 
                                    * ((unsigned int)
705
 
                                       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
706
735
          if(new_argv == NULL){
707
 
            error(0, errno, "realloc");
 
736
            error(0, errno, "reallocarray");
708
737
            exitstatus = EX_OSERR;
709
738
            free(new_arg);
710
739
            free(org_line);
794
823
  }
795
824
  
796
825
  if(debug){
797
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
826
    for(plugin *p = plugin_list; p != NULL; p = p->next){
798
827
      fprintf(stderr, "Plugin: %s has %d arguments\n",
799
828
              p->name ? p->name : "Global", p->argc - 1);
800
829
      for(char **a = p->argv; *a != NULL; a++){
809
838
  
810
839
  if(getuid() == 0){
811
840
    /* Work around Debian bug #633582:
812
 
       <http://bugs.debian.org/633582> */
 
841
       <https://bugs.debian.org/633582> */
813
842
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
814
843
    if(plugindir_fd == -1){
815
844
      if(errno != ENOENT){
827
856
          }
828
857
        }
829
858
      }
830
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
859
      close(plugindir_fd);
831
860
    }
832
861
  }
833
862
  
892
921
    return 1;
893
922
  }
894
923
  
895
 
#ifdef __GLIBC__
896
 
#if __GLIBC_PREREQ(2, 15)
897
924
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
898
925
                             alphasort);
899
 
#else  /* not __GLIBC_PREREQ(2, 15) */
900
 
  int numplugins = scandir(plugindir != NULL ? plugindir : PDIR,
901
 
                           &direntries, good_name, alphasort);
902
 
#endif  /* not __GLIBC_PREREQ(2, 15) */
903
 
#else   /* not __GLIBC__ */
904
 
  int numplugins = scandir(plugindir != NULL ? plugindir : PDIR,
905
 
                           &direntries, good_name, alphasort);
906
 
#endif  /* not __GLIBC__ */
907
926
  if(numplugins == -1){
908
927
    error(0, errno, "Could not scan plugin dir");
909
928
    direntries = NULL;
925
944
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
926
945
    if(ret == -1){
927
946
      error(0, errno, "stat");
928
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
947
      close(plugin_fd);
929
948
      free(direntries[i]);
930
949
      continue;
931
950
    }
940
959
                plugindir != NULL ? plugindir : PDIR,
941
960
                direntries[i]->d_name);
942
961
      }
943
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
962
      close(plugin_fd);
944
963
      free(direntries[i]);
945
964
      continue;
946
965
    }
948
967
    plugin *p = getplugin(direntries[i]->d_name);
949
968
    if(p == NULL){
950
969
      error(0, errno, "getplugin");
951
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
970
      close(plugin_fd);
952
971
      free(direntries[i]);
953
972
      continue;
954
973
    }
957
976
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
958
977
                direntries[i]->d_name);
959
978
      }
960
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
979
      close(plugin_fd);
961
980
      free(direntries[i]);
962
981
      continue;
963
982
    }
1003
1022
    if(pipefd[0] >= FD_SETSIZE){
1004
1023
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
1005
1024
              FD_SETSIZE);
1006
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
1007
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1025
      close(pipefd[0]);
 
1026
      close(pipefd[1]);
1008
1027
      exitstatus = EX_OSERR;
1009
1028
      free(direntries[i]);
1010
1029
      goto fallback;
1014
1033
    ret = set_cloexec_flag(pipefd[0]);
1015
1034
    if(ret < 0){
1016
1035
      error(0, errno, "set_cloexec_flag");
1017
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
1018
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1036
      close(pipefd[0]);
 
1037
      close(pipefd[1]);
1019
1038
      exitstatus = EX_OSERR;
1020
1039
      free(direntries[i]);
1021
1040
      goto fallback;
1023
1042
    ret = set_cloexec_flag(pipefd[1]);
1024
1043
    if(ret < 0){
1025
1044
      error(0, errno, "set_cloexec_flag");
1026
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
1027
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
1028
1047
      exitstatus = EX_OSERR;
1029
1048
      free(direntries[i]);
1030
1049
      goto fallback;
1049
1068
      error(0, errno, "fork");
1050
1069
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1051
1070
                                     &sigchld_action.sa_mask, NULL));
1052
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
1053
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1071
      close(pipefd[0]);
 
1072
      close(pipefd[1]);
1054
1073
      exitstatus = EX_OSERR;
1055
1074
      free(direntries[i]);
1056
1075
      goto fallback;
1084
1103
      /* no return */
1085
1104
    }
1086
1105
    /* Parent process */
1087
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1088
 
                                             pipe */
1089
 
    TEMP_FAILURE_RETRY(close(plugin_fd));
 
1106
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1107
    close(plugin_fd);
1090
1108
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1091
1109
    if(new_plugin == NULL){
1092
1110
      error(0, errno, "getplugin");
1104
1122
    
1105
1123
    new_plugin->pid = pid;
1106
1124
    new_plugin->fd = pipefd[0];
1107
 
    
 
1125
 
 
1126
    if(debug){
 
1127
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1128
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1129
    }
 
1130
 
1108
1131
    /* Unblock SIGCHLD so signal handler can be run if this process
1109
1132
       has already completed */
1110
1133
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1116
1139
      goto fallback;
1117
1140
    }
1118
1141
    
1119
 
#if defined (__GNUC__) and defined (__GLIBC__)
1120
 
#if not __GLIBC_PREREQ(2, 16)
1121
 
#pragma GCC diagnostic push
1122
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1123
 
#endif
1124
 
#endif
1125
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1126
 
                                          -Wconversion in GNU libc
1127
 
                                          before 2.16 */
1128
 
#if defined (__GNUC__) and defined (__GLIBC__)
1129
 
#if not __GLIBC_PREREQ(2, 16)
1130
 
#pragma GCC diagnostic pop
1131
 
#endif
1132
 
#endif
 
1142
    FD_SET(new_plugin->fd, &rfds_all);
1133
1143
    
1134
1144
    if(maxfd < new_plugin->fd){
1135
1145
      maxfd = new_plugin->fd;
1138
1148
  
1139
1149
  free(direntries);
1140
1150
  direntries = NULL;
1141
 
  TEMP_FAILURE_RETRY(close(dir_fd));
 
1151
  close(dir_fd);
1142
1152
  dir_fd = -1;
1143
1153
  free_plugin(getplugin(NULL));
1144
1154
  
1184
1194
                      (intmax_t) (proc->pid),
1185
1195
                      WTERMSIG(proc->status),
1186
1196
                      strsignal(WTERMSIG(proc->status)));
1187
 
            } else if(WCOREDUMP(proc->status)){
1188
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1189
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1190
1197
            }
1191
1198
          }
1192
1199
          
1193
1200
          /* Remove the plugin */
1194
 
#if defined (__GNUC__) and defined (__GLIBC__)
1195
 
#if not __GLIBC_PREREQ(2, 16)
1196
 
#pragma GCC diagnostic push
1197
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1198
 
#endif
1199
 
#endif
1200
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1201
 
                                          -Wconversion in GNU libc
1202
 
                                          before 2.16 */
1203
 
#if defined (__GNUC__) and defined (__GLIBC__)
1204
 
#if not __GLIBC_PREREQ(2, 16)
1205
 
#pragma GCC diagnostic pop
1206
 
#endif
1207
 
#endif
 
1201
          FD_CLR(proc->fd, &rfds_all);
1208
1202
          
1209
1203
          /* Block signal while modifying process_list */
1210
1204
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1250
1244
      }
1251
1245
      
1252
1246
      /* This process has not completed.  Does it have any output? */
1253
 
#if defined (__GNUC__) and defined (__GLIBC__)
1254
 
#if not __GLIBC_PREREQ(2, 16)
1255
 
#pragma GCC diagnostic push
1256
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1257
 
#endif
1258
 
#endif
1259
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1260
 
                                                         warning from
1261
 
                                                         -Wconversion
1262
 
                                                         in GNU libc
1263
 
                                                         before
1264
 
                                                         2.16 */
1265
 
#if defined (__GNUC__) and defined (__GLIBC__)
1266
 
#if not __GLIBC_PREREQ(2, 16)
1267
 
#pragma GCC diagnostic pop
1268
 
#endif
1269
 
#endif
 
1247
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1270
1248
        /* This process had nothing to say at this time */
1271
1249
        proc = proc->next;
1272
1250
        continue;
1342
1320
  free(direntries);
1343
1321
  
1344
1322
  if(dir_fd != -1){
1345
 
    TEMP_FAILURE_RETRY(close(dir_fd));
 
1323
    close(dir_fd);
1346
1324
  }
1347
1325
  
1348
1326
  /* Kill the processes */