/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 plugins.d/plymouth.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
 * Plymouth - Read a password from Plymouth and output it
4
4
 * 
5
 
 * Copyright © 2010-2014 Teddy Hogeborn
6
 
 * Copyright © 2010-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 © 2010-2019 Teddy Hogeborn
 
6
 * Copyright © 2010-2019 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
 */
43
44
                                   STDERR_FILENO, execv(), access() */
44
45
#include <stdlib.h>             /* free(), EXIT_FAILURE, realloc(),
45
46
                                   EXIT_SUCCESS, malloc(), _exit(),
46
 
                                   getenv() */
 
47
                                   getenv(), reallocarray() */
47
48
#include <dirent.h>             /* scandir(), alphasort() */
48
49
#include <inttypes.h>           /* intmax_t, strtoumax(), SCNuMAX */
49
50
#include <sys/stat.h>           /* struct stat, lstat() */
52
53
#include <errno.h>              /* TEMP_FAILURE_RETRY */
53
54
#include <argz.h>               /* argz_count(), argz_extract() */
54
55
#include <stdarg.h>             /* va_list, va_start(), ... */
 
56
#include <argp.h>
55
57
 
56
58
sig_atomic_t interrupted_by_signal = 0;
 
59
const char *argp_program_version = "plymouth " VERSION;
 
60
const char *argp_program_bug_address = "<mandos@recompile.se>";
57
61
 
58
62
/* Used by Ubuntu 11.04 (Natty Narwahl) */
59
 
const char plymouth_old_pid[] = "/dev/.initramfs/plymouth.pid";
 
63
const char plymouth_old_old_pid[] = "/dev/.initramfs/plymouth.pid";
60
64
/* Used by Ubuntu 11.10 (Oneiric Ocelot) */
61
 
const char plymouth_pid[] = "/run/initramfs/plymouth.pid";
 
65
const char plymouth_old_pid[] = "/run/initramfs/plymouth.pid";
 
66
/* Used by Debian 9 (stretch) */
 
67
const char plymouth_pid[] = "/run/plymouth/pid";
62
68
 
63
69
const char plymouth_path[] = "/bin/plymouth";
64
70
const char plymouthd_path[] = "/sbin/plymouthd";
66
72
                                        "--mode=boot",
67
73
                                        "--attach-to-session",
68
74
                                        NULL };
 
75
bool debug = false;
69
76
 
70
77
static void termination_handler(__attribute__((unused))int signum){
71
78
  if(interrupted_by_signal){
74
81
  interrupted_by_signal = 1;
75
82
}
76
83
 
 
84
__attribute__((format (gnu_printf, 2, 3), nonnull))
 
85
int fprintf_plus(FILE *stream, const char *format, ...){
 
86
  va_list ap;
 
87
  va_start (ap, format);
 
88
  fprintf(stream, "Mandos plugin %s: ", program_invocation_short_name);
 
89
  return vfprintf(stream, format, ap);
 
90
}
 
91
 
77
92
/* Function to use when printing errors */
78
93
__attribute__((format (gnu_printf, 3, 4)))
79
94
void error_plus(int status, int errnum, const char *formatstring,
156
171
 
157
172
__attribute__((nonnull (2, 3)))
158
173
bool exec_and_wait(pid_t *pid_return, const char *path,
159
 
                   const char * const *argv, bool interruptable,
 
174
                   const char * const * const argv, bool interruptable,
160
175
                   bool daemonize){
161
176
  int status;
162
177
  int ret;
163
178
  pid_t pid;
 
179
  if(debug){
 
180
    for(const char * const *arg = argv; *arg != NULL; arg++){
 
181
      fprintf_plus(stderr, "exec_and_wait arg: %s\n", *arg);
 
182
    }
 
183
    fprintf_plus(stderr, "exec_and_wait end of args\n");
 
184
  }
 
185
 
164
186
  pid = fork();
165
187
  if(pid == -1){
166
188
    error_plus(0, errno, "fork");
174
196
      }
175
197
    }
176
198
    
177
 
    char **new_argv = NULL;
 
199
    char **new_argv = malloc(sizeof(const char *));
 
200
    if(new_argv == NULL){
 
201
      error_plus(0, errno, "malloc");
 
202
      _exit(EX_OSERR);
 
203
    }
178
204
    char **tmp;
179
205
    int i = 0;
180
 
    for (; argv[i]!=NULL; i++){
181
 
      tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 1));
 
206
    for (; argv[i] != NULL; i++){
 
207
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
 
208
      tmp = reallocarray(new_argv, ((size_t)i + 2),
 
209
                         sizeof(const char *));
 
210
#else
 
211
      if(((size_t)i + 2) > (SIZE_MAX / sizeof(const char *))){
 
212
        /* overflow */
 
213
        tmp = NULL;
 
214
        errno = ENOMEM;
 
215
      } else {
 
216
        tmp = realloc(new_argv, ((size_t)i + 2) * sizeof(const char *));
 
217
      }
 
218
#endif
182
219
      if(tmp == NULL){
183
 
        error_plus(0, errno, "realloc");
 
220
        error_plus(0, errno, "reallocarray");
184
221
        free(new_argv);
185
222
        _exit(EX_OSERR);
186
223
      }
202
239
          and ((not interrupted_by_signal)
203
240
               or (not interruptable)));
204
241
  if(interrupted_by_signal and interruptable){
 
242
    if(debug){
 
243
      fprintf_plus(stderr, "Interrupted by signal\n");
 
244
    }
205
245
    return false;
206
246
  }
207
247
  if(ret == -1){
208
248
    error_plus(0, errno, "waitpid");
209
249
    return false;
210
250
  }
 
251
  if(debug){
 
252
    if(WIFEXITED(status)){
 
253
      fprintf_plus(stderr, "exec_and_wait exited: %d\n",
 
254
                   WEXITSTATUS(status));
 
255
    } else if(WIFSIGNALED(status)) {
 
256
      fprintf_plus(stderr, "exec_and_wait signaled: %d\n",
 
257
                   WTERMSIG(status));
 
258
    }
 
259
  }
211
260
  if(WIFEXITED(status) and (WEXITSTATUS(status) == 0)){
212
261
    return true;
213
262
  }
277
326
  }
278
327
  /* Try the old pid file location */
279
328
  if(proc_id == 0){
280
 
    pidfile = fopen(plymouth_pid, "r");
 
329
    pidfile = fopen(plymouth_old_pid, "r");
 
330
    if(pidfile != NULL){
 
331
      ret = fscanf(pidfile, "%" SCNuMAX, &proc_id);
 
332
      if(ret != 1){
 
333
        proc_id = 0;
 
334
      }
 
335
      fclose(pidfile);
 
336
    }
 
337
  }
 
338
  /* Try the old old pid file location */
 
339
  if(proc_id == 0){
 
340
    pidfile = fopen(plymouth_old_old_pid, "r");
281
341
    if(pidfile != NULL){
282
342
      ret = fscanf(pidfile, "%" SCNuMAX, &proc_id);
283
343
      if(ret != 1){
294
354
      error_plus(0, errno, "scandir");
295
355
    }
296
356
    if(ret > 0){
297
 
      ret = sscanf(direntries[0]->d_name, "%" SCNuMAX, &proc_id);
298
 
      if(ret < 0){
299
 
        error_plus(0, errno, "sscanf");
 
357
      for(int i = ret-1; i >= 0; i--){
 
358
        if(proc_id == 0){
 
359
          ret = sscanf(direntries[i]->d_name, "%" SCNuMAX, &proc_id);
 
360
          if(ret < 0){
 
361
            error_plus(0, errno, "sscanf");
 
362
          }
 
363
        }
 
364
        free(direntries[i]);
300
365
      }
301
366
    }
302
367
    /* scandir might preallocate for this variable (man page unclear).
312
377
  return 0;
313
378
}
314
379
 
315
 
const char * const * getargv(pid_t pid){
 
380
char **getargv(pid_t pid){
316
381
  int cl_fd;
317
382
  char *cmdline_filename;
318
383
  ssize_t sret;
379
444
    return NULL;
380
445
  }
381
446
  argz_extract(cmdline, cmdline_len, argv); /* Create argv */
382
 
  return (const char * const *)argv;
 
447
  return argv;
383
448
}
384
449
 
385
450
int main(__attribute__((unused))int argc,
386
451
         __attribute__((unused))char **argv){
387
 
  char *prompt;
 
452
  char *prompt = NULL;
388
453
  char *prompt_arg;
389
454
  pid_t plymouth_command_pid;
390
455
  int ret;
391
456
  bool bret;
392
457
 
 
458
  {
 
459
    struct argp_option options[] = {
 
460
      { .name = "prompt", .key = 128, .arg = "PROMPT",
 
461
        .doc = "The prompt to show" },
 
462
      { .name = "debug", .key = 129,
 
463
        .doc = "Debug mode" },
 
464
      { .name = NULL }
 
465
    };
 
466
    
 
467
    __attribute__((nonnull(3)))
 
468
    error_t parse_opt (int key, char *arg, __attribute__((unused))
 
469
                       struct argp_state *state){
 
470
      errno = 0;
 
471
      switch (key){
 
472
      case 128:                 /* --prompt */
 
473
        prompt = arg;
 
474
        if(debug){
 
475
          fprintf_plus(stderr, "Custom prompt \"%s\"\n", prompt);
 
476
        }
 
477
        break;
 
478
      case 129:                 /* --debug */
 
479
        debug = true;
 
480
        break;
 
481
      default:
 
482
        return ARGP_ERR_UNKNOWN;
 
483
      }
 
484
      return errno;
 
485
    }
 
486
    
 
487
    struct argp argp = { .options = options, .parser = parse_opt,
 
488
                         .args_doc = "",
 
489
                         .doc = "Mandos plymouth -- Read and"
 
490
                         " output a password" };
 
491
    ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, NULL);
 
492
    switch(ret){
 
493
    case 0:
 
494
      break;
 
495
    case ENOMEM:
 
496
    default:
 
497
      errno = ret;
 
498
      error_plus(0, errno, "argp_parse");
 
499
      return EX_OSERR;
 
500
    case EINVAL:
 
501
      error_plus(0, errno, "argp_parse");
 
502
      return EX_USAGE;
 
503
    }
 
504
  }
 
505
  
393
506
  /* test -x /bin/plymouth */
394
507
  ret = access(plymouth_path, X_OK);
395
508
  if(ret == -1){
396
509
    /* Plymouth is probably not installed.  Don't print an error
397
510
       message, just exit. */
 
511
    if(debug){
 
512
      fprintf_plus(stderr, "Plymouth (%s) not found\n",
 
513
                   plymouth_path);
 
514
    }
398
515
    exit(EX_UNAVAILABLE);
399
516
  }
400
517
  
434
551
    }
435
552
    /* Plymouth is probably not running.  Don't print an error
436
553
       message, just exit. */
 
554
    if(debug){
 
555
      fprintf_plus(stderr, "Plymouth not running\n");
 
556
    }
437
557
    exit(EX_UNAVAILABLE);
438
558
  }
439
559
  
440
 
  prompt = makeprompt();
441
 
  ret = asprintf(&prompt_arg, "--prompt=%s", prompt);
442
 
  free(prompt);
 
560
  if(prompt != NULL){
 
561
    ret = asprintf(&prompt_arg, "--prompt=%s", prompt);
 
562
  } else {
 
563
    char *made_prompt = makeprompt();
 
564
    ret = asprintf(&prompt_arg, "--prompt=%s", made_prompt);
 
565
    free(made_prompt);
 
566
  }
443
567
  if(ret == -1){
444
568
    error_plus(EX_OSERR, errno, "asprintf");
445
569
  }
446
570
  
447
571
  /* plymouth ask-for-password --prompt="$prompt" */
 
572
  if(debug){
 
573
    fprintf_plus(stderr, "Prompting for password via Plymouth\n");
 
574
  }
448
575
  bret = exec_and_wait(&plymouth_command_pid,
449
576
                       plymouth_path, (const char *[])
450
577
                       { plymouth_path, "ask-for-password",
460
587
  }
461
588
  kill_and_wait(plymouth_command_pid);
462
589
  
463
 
  const char * const *plymouthd_argv;
 
590
  char **plymouthd_argv = NULL;
464
591
  pid_t pid = get_pid();
465
592
  if(pid == 0){
466
593
    error_plus(0, 0, "plymouthd pid not found");
467
 
    plymouthd_argv = plymouthd_default_argv;
468
594
  } else {
469
595
    plymouthd_argv = getargv(pid);
470
596
  }
473
599
                       { plymouth_path, "quit", NULL },
474
600
                       false, false);
475
601
  if(not bret){
 
602
    if(plymouthd_argv != NULL){
 
603
      free(*plymouthd_argv);
 
604
      free(plymouthd_argv);
 
605
    }
476
606
    exit(EXIT_FAILURE);
477
607
  }
478
 
  bret = exec_and_wait(NULL, plymouthd_path, plymouthd_argv,
 
608
  bret = exec_and_wait(NULL, plymouthd_path,
 
609
                       (plymouthd_argv != NULL)
 
610
                       ? (const char * const *)plymouthd_argv
 
611
                       : plymouthd_default_argv,
479
612
                       false, true);
 
613
  if(plymouthd_argv != NULL){
 
614
    free(*plymouthd_argv);
 
615
    free(plymouthd_argv);
 
616
  }
480
617
  if(not bret){
481
618
    exit(EXIT_FAILURE);
482
619
  }