/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 dracut-module/password-agent.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 password agent - Simple password agent to run Mandos client
4
4
 *
5
 
 * Copyright © 2019-2021 Teddy Hogeborn
6
 
 * Copyright © 2019-2021 Björn Påhlsson
 
5
 * Copyright © 2019 Teddy Hogeborn
 
6
 * Copyright © 2019 Björn Påhlsson
7
7
 * 
8
8
 * This file is part of Mandos.
9
9
 * 
23
23
 * Contact the authors at <mandos@recompile.se>.
24
24
 */
25
25
 
26
 
#define _GNU_SOURCE             /* pipe2(), O_CLOEXEC, setresgid(),
27
 
                                   setresuid(), asprintf(), getline(),
28
 
                                   basename() */
29
 
#include <inttypes.h>           /* uintmax_t, strtoumax(), PRIuMAX,
30
 
                                   PRIdMAX, intmax_t, uint32_t,
31
 
                                   SCNx32, SCNuMAX, SCNxMAX */
32
 
#include <stddef.h>             /* size_t, NULL */
 
26
#define _GNU_SOURCE
 
27
#include <inttypes.h>           /* uintmax_t, PRIuMAX, PRIdMAX,
 
28
                                   intmax_t, uint32_t, SCNx32,
 
29
                                   SCNuMAX, SCNxMAX */
 
30
#include <stddef.h>             /* size_t */
33
31
#include <sys/types.h>          /* pid_t, uid_t, gid_t, getuid(),
34
32
                                   getpid() */
35
33
#include <stdbool.h>            /* bool, true, false */
42
40
                                   NSIG, sigismember(), SA_ONSTACK,
43
41
                                   SIG_DFL, SIG_IGN, SIGINT, SIGQUIT,
44
42
                                   SIGHUP, SIGSTOP, SIG_UNBLOCK */
45
 
#include <unistd.h>             /* uid_t, gid_t, close(), pipe2(),
46
 
                                   fork(), _exit(), dup2(),
47
 
                                   STDOUT_FILENO, setresgid(),
48
 
                                   setresuid(), execv(), ssize_t,
49
 
                                   read(), dup3(), getuid(), dup(),
50
 
                                   STDERR_FILENO, pause(), write(),
51
 
                                   rmdir(), unlink(), getpid() */
52
43
#include <stdlib.h>             /* EXIT_SUCCESS, EXIT_FAILURE,
53
 
                                   malloc(), free(), realloc(),
54
 
                                   setenv(), calloc(), mkdtemp(),
55
 
                                   mkostemp() */
 
44
                                   malloc(), free(), strtoumax(),
 
45
                                   realloc(), setenv(), calloc(),
 
46
                                   mkdtemp(), mkostemp() */
56
47
#include <iso646.h>             /* not, or, and, xor */
57
48
#include <error.h>              /* error() */
58
49
#include <sysexits.h>           /* EX_USAGE, EX_OSERR, EX_OSFILE */
66
57
#include <string.h>             /* strdup(), memcpy(),
67
58
                                   explicit_bzero(), memset(),
68
59
                                   strcmp(), strlen(), strncpy(),
69
 
                                   memcmp(), basename(), strerror() */
 
60
                                   memcmp(), basename() */
70
61
#include <argz.h>               /* argz_create(), argz_count(),
71
62
                                   argz_extract(), argz_next(),
72
63
                                   argz_add() */
82
73
                                   ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS,
83
74
                                   struct argp, argp_parse(),
84
75
                                   ARGP_NO_EXIT */
85
 
#include <stdint.h>             /* SIZE_MAX, uint32_t */
 
76
#include <stdint.h>             /* SIZE_MAX */
 
77
#include <unistd.h>             /* uid_t, gid_t, close(), pipe2(),
 
78
                                   fork(), _exit(), dup2(),
 
79
                                   STDOUT_FILENO, setresgid(),
 
80
                                   setresuid(), execv(), ssize_t,
 
81
                                   read(), dup3(), getuid(), dup(),
 
82
                                   STDERR_FILENO, pause(), write(),
 
83
                                   rmdir(), unlink(), getpid() */
86
84
#include <sys/mman.h>           /* munlock(), mlock() */
87
85
#include <fcntl.h>              /* O_CLOEXEC, O_NONBLOCK, fcntl(),
88
86
                                   F_GETFD, F_GETFL, FD_CLOEXEC,
112
110
                        g_assert_null(), g_assert_false(),
113
111
                        g_assert_cmpint(), g_assert_cmpuint(),
114
112
                        g_test_skip(), g_assert_cmpstr(),
115
 
                        g_test_message(), g_test_init(), g_test_add(),
116
 
                        g_test_run(), GOptionContext,
117
 
                        g_option_context_new(),
 
113
                        g_test_init(), g_test_add(), g_test_run(),
 
114
                        GOptionContext, g_option_context_new(),
118
115
                        g_option_context_set_help_enabled(), FALSE,
119
116
                        g_option_context_set_ignore_unknown_options(),
120
117
                        gboolean, GOptionEntry, G_OPTION_ARG_NONE,
1196
1193
  bool *const password_is_read = task.password_is_read;
1197
1194
 
1198
1195
  /* We use the GLib "Key-value file parser" functions to parse the
1199
 
     question file.  See <https://systemd.io/PASSWORD_AGENTS/> for
1200
 
     specification of contents */
 
1196
     question file.  See <https://www.freedesktop.org/wiki/Software
 
1197
     /systemd/PasswordAgents/> for specification of contents */
1201
1198
  __attribute__((nonnull))
1202
1199
    void cleanup_g_key_file(GKeyFile **key_file){
1203
1200
    if(*key_file != NULL){
1493
1490
         not. You may but don't have to include a final NUL byte in
1494
1491
         your message.
1495
1492
 
1496
 
         — <https://systemd.io/PASSWORD_AGENTS/> (Tue, 15 Sep 2020
1497
 
         14:24:20 GMT)
 
1493
         — <https://www.freedesktop.org/wiki/Software/systemd/
 
1494
         PasswordAgents/> (Wed 08 Oct 2014 02:14:28 AM UTC)
1498
1495
      */
1499
1496
      send_buffer[0] = '+';     /* Prefix with "+" */
1500
1497
      /* Always add an extra NUL */
1505
1502
      errno = 0;
1506
1503
      ssize_t ssret = send(fd, send_buffer, send_buffer_length,
1507
1504
                           MSG_NOSIGNAL);
1508
 
      const error_t saved_errno = (ssret < 0) ? errno : 0;
 
1505
      const error_t saved_errno = errno;
1509
1506
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
1510
1507
      explicit_bzero(send_buffer, send_buffer_length);
1511
1508
#else
1529
1526
          /* Retry, below */
1530
1527
          break;
1531
1528
        case EMSGSIZE:
1532
 
          error(0, saved_errno, "Password of size %" PRIuMAX
1533
 
                " is too big", (uintmax_t)password->length);
 
1529
          error(0, 0, "Password of size %" PRIuMAX " is too big",
 
1530
                (uintmax_t)password->length);
1534
1531
#if __GNUC__ < 7
1535
1532
          /* FALLTHROUGH */
1536
1533
#else
1538
1535
#endif
1539
1536
        case 0:
1540
1537
          if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){
1541
 
            error(0, 0, "Password only partially sent to socket %s: %"
1542
 
                  PRIuMAX " out of %" PRIuMAX " bytes sent", filename,
1543
 
                  (uintmax_t)ssret, (uintmax_t)send_buffer_length);
 
1538
            error(0, 0, "Password only partially sent to socket");
1544
1539
          }
1545
1540
#if __GNUC__ < 7
1546
1541
          /* FALLTHROUGH */
5812
5807
  char write_data[PIPE_BUF];
5813
5808
  {
5814
5809
    /* Construct test password buffer */
5815
 
    /* Start with + since that is what the real protocol uses */
 
5810
    /* Start with + since that is what the real procotol uses */
5816
5811
    write_data[0] = '+';
5817
5812
    /* Set a special character at string end just to mark the end */
5818
5813
    write_data[sizeof(write_data)-2] = 'y';
5963
5958
                                           test_fixture *fixture,
5964
5959
                                           __attribute__((unused))
5965
5960
                                           gconstpointer user_data){
 
5961
#ifndef __amd64__
 
5962
  g_test_skip("Skipping EMSGSIZE test on non-AMD64 platform");
 
5963
#else
5966
5964
  __attribute__((cleanup(cleanup_close)))
5967
5965
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5968
5966
  g_assert_cmpint(epoll_fd, >=, 0);
5970
5968
  char *const filename = strdup("/nonexistent/socket");
5971
5969
  __attribute__((cleanup(string_set_clear)))
5972
5970
    string_set cancelled_filenames = {};
5973
 
  int socketfds[2];
5974
 
 
5975
 
  /* Find a message size which triggers EMSGSIZE */
5976
 
  __attribute__((cleanup(cleanup_string)))
5977
 
    char *message_buffer = NULL;
5978
 
  size_t message_size = PIPE_BUF + 1;
5979
 
  for(ssize_t ssret = 0; ssret >= 0; message_size += 1024){
5980
 
    if(message_size >= 1024*1024*1024){ /* 1 GiB */
5981
 
      g_test_skip("Skipping EMSGSIZE test: Will not try 1GiB");
5982
 
      return;
5983
 
    }
5984
 
    message_buffer = realloc(message_buffer, message_size);
5985
 
    if(message_buffer == NULL){
5986
 
      g_test_skip("Skipping EMSGSIZE test");
5987
 
      g_test_message("Failed to malloc() %" PRIuMAX " bytes",
5988
 
                     (uintmax_t)message_size);
5989
 
      return;
5990
 
    }
5991
 
    /* Fill buffer with 'x' */
5992
 
    memset(message_buffer, 'x', message_size);
5993
 
    /* Create a new socketpair for each message size to avoid having
5994
 
       to empty the pipe by reading the message to a separate buffer
5995
 
    */
5996
 
    g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
5997
 
                               | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
5998
 
                               socketfds), ==, 0);
5999
 
    ssret = send(socketfds[1], message_buffer, message_size,
6000
 
                 MSG_NOSIGNAL);
6001
 
    error_t saved_errno = errno;
6002
 
    g_assert_cmpint(close(socketfds[0]), ==, 0);
6003
 
    g_assert_cmpint(close(socketfds[1]), ==, 0);
6004
 
 
6005
 
    if(ssret < 0){
6006
 
      if(saved_errno != EMSGSIZE) {
6007
 
        g_test_skip("Skipping EMSGSIZE test");
6008
 
        g_test_message("Error on send(%" PRIuMAX " bytes): %s",
6009
 
                       (uintmax_t)message_size,
6010
 
                       strerror(saved_errno));
6011
 
        return;
6012
 
      }
6013
 
      break;
6014
 
    } else if(ssret != (ssize_t)message_size){
6015
 
      g_test_skip("Skipping EMSGSIZE test");
6016
 
      g_test_message("Partial send(): %" PRIuMAX " of %" PRIdMAX
6017
 
                     " bytes", (uintmax_t)ssret,
6018
 
                     (intmax_t)message_size);
6019
 
      return;
6020
 
    }
6021
 
  }
6022
 
  g_test_message("EMSGSIZE triggered by %" PRIdMAX " bytes",
6023
 
                 (intmax_t)message_size);
6024
 
 
6025
 
  buffer password = {
6026
 
    .data=message_buffer,
6027
 
    .length=message_size - 2,   /* Compensate for added '+' and NUL */
6028
 
    .allocated=message_size,
 
5971
  const size_t oversized = 1024*1024; /* Limit seems to be 212960 */
 
5972
  __attribute__((cleanup(cleanup_buffer)))
 
5973
    buffer password = {
 
5974
    .data=malloc(oversized),
 
5975
    .length=oversized,
 
5976
    .allocated=oversized,
6029
5977
  };
 
5978
  g_assert_nonnull(password.data);
6030
5979
  if(mlock(password.data, password.allocated) != 0){
6031
5980
    g_assert_true(errno == EPERM or errno == ENOMEM);
6032
5981
  }
 
5982
  /* Construct test password buffer */
 
5983
  /* Start with + since that is what the real procotol uses */
 
5984
  password.data[0] = '+';
 
5985
  /* Set a special character at string end just to mark the end */
 
5986
  password.data[oversized-3] = 'y';
 
5987
  /* Set NUL at buffer end, as suggested by the protocol */
 
5988
  password.data[oversized-2] = '\0';
 
5989
  /* Fill rest of password with 'x' */
 
5990
  memset(password.data+1, 'x', oversized-3);
6033
5991
 
6034
5992
  __attribute__((cleanup(cleanup_queue)))
6035
5993
    task_queue *queue = create_queue();
6036
5994
  g_assert_nonnull(queue);
 
5995
  int socketfds[2];
6037
5996
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
6038
5997
                             | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
6039
5998
                             socketfds), ==, 0);
6060
6019
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
6061
6020
  g_assert_true(string_set_contains(cancelled_filenames,
6062
6021
                                    question_filename));
 
6022
#endif
6063
6023
}
6064
6024
 
6065
6025
static void test_send_password_to_socket_retry(__attribute__((unused))