/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 mandos

  • 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:
11
11
# "AvahiService" class, and some lines in "main".
12
12
#
13
13
# Everything else is
14
 
# Copyright © 2008-2020 Teddy Hogeborn
15
 
# Copyright © 2008-2020 Björn Påhlsson
 
14
# Copyright © 2008-2019 Teddy Hogeborn
 
15
# Copyright © 2008-2019 Björn Påhlsson
16
16
#
17
17
# This file is part of Mandos.
18
18
#
143
143
if sys.version_info < (3, 2):
144
144
    configparser.Configparser = configparser.SafeConfigParser
145
145
 
146
 
version = "1.8.14"
 
146
version = "1.8.10"
147
147
stored_state_file = "clients.pickle"
148
148
 
149
149
logger = logging.getLogger()
524
524
class AvahiServiceToSyslog(AvahiService):
525
525
    def rename(self, *args, **kwargs):
526
526
        """Add the new name to the syslog messages"""
527
 
        ret = super(AvahiServiceToSyslog, self).rename(*args,
528
 
                                                       **kwargs)
 
527
        ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
529
528
        syslogger.setFormatter(logging.Formatter(
530
529
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
531
530
            .format(self.name)))
563
562
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
564
563
 
565
564
    # Types
566
 
    class _session_int(ctypes.Structure):
 
565
    class session_int(ctypes.Structure):
567
566
        _fields_ = []
568
 
    session_t = ctypes.POINTER(_session_int)
 
567
    session_t = ctypes.POINTER(session_int)
569
568
 
570
569
    class certificate_credentials_st(ctypes.Structure):
571
570
        _fields_ = []
577
576
        _fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
578
577
                    ('size', ctypes.c_uint)]
579
578
 
580
 
    class _openpgp_crt_int(ctypes.Structure):
 
579
    class openpgp_crt_int(ctypes.Structure):
581
580
        _fields_ = []
582
 
    openpgp_crt_t = ctypes.POINTER(_openpgp_crt_int)
 
581
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
583
582
    openpgp_crt_fmt_t = ctypes.c_int  # gnutls/openpgp.h
584
583
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
585
584
    credentials_type_t = ctypes.c_int
594
593
            # gnutls.strerror()
595
594
            self.code = code
596
595
            if message is None and code is not None:
597
 
                message = gnutls.strerror(code).decode(
598
 
                    "utf-8", errors="replace")
 
596
                message = gnutls.strerror(code)
599
597
            return super(gnutls.Error, self).__init__(
600
598
                message, *args)
601
599
 
602
600
    class CertificateSecurityError(Error):
603
601
        pass
604
602
 
605
 
    class PointerTo:
606
 
        def __init__(self, cls):
607
 
            self.cls = cls
608
 
 
609
 
        def from_param(self, obj):
610
 
            if not isinstance(obj, self.cls):
611
 
                raise TypeError("Not of type {}: {!r}"
612
 
                                .format(self.cls.__name__, obj))
613
 
            return ctypes.byref(obj.from_param(obj))
614
 
 
615
 
    class CastToVoidPointer:
616
 
        def __init__(self, cls):
617
 
            self.cls = cls
618
 
 
619
 
        def from_param(self, obj):
620
 
            if not isinstance(obj, self.cls):
621
 
                raise TypeError("Not of type {}: {!r}"
622
 
                                .format(self.cls.__name__, obj))
623
 
            return ctypes.cast(obj.from_param(obj), ctypes.c_void_p)
624
 
 
625
 
    class With_from_param:
626
 
        @classmethod
627
 
        def from_param(cls, obj):
628
 
            return obj._as_parameter_
629
 
 
630
603
    # Classes
631
 
    class Credentials(With_from_param):
 
604
    class Credentials:
632
605
        def __init__(self):
633
 
            self._as_parameter_ = gnutls.certificate_credentials_t()
634
 
            gnutls.certificate_allocate_credentials(self)
 
606
            self._c_object = gnutls.certificate_credentials_t()
 
607
            gnutls.certificate_allocate_credentials(
 
608
                ctypes.byref(self._c_object))
635
609
            self.type = gnutls.CRD_CERTIFICATE
636
610
 
637
611
        def __del__(self):
638
 
            gnutls.certificate_free_credentials(self)
 
612
            gnutls.certificate_free_credentials(self._c_object)
639
613
 
640
 
    class ClientSession(With_from_param):
 
614
    class ClientSession:
641
615
        def __init__(self, socket, credentials=None):
642
 
            self._as_parameter_ = gnutls.session_t()
 
616
            self._c_object = gnutls.session_t()
643
617
            gnutls_flags = gnutls.CLIENT
644
618
            if gnutls.check_version(b"3.5.6"):
645
619
                gnutls_flags |= gnutls.NO_TICKETS
646
620
            if gnutls.has_rawpk:
647
621
                gnutls_flags |= gnutls.ENABLE_RAWPK
648
 
            gnutls.init(self, gnutls_flags)
 
622
            gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
649
623
            del gnutls_flags
650
 
            gnutls.set_default_priority(self)
651
 
            gnutls.transport_set_ptr(self, socket.fileno())
652
 
            gnutls.handshake_set_private_extensions(self, True)
 
624
            gnutls.set_default_priority(self._c_object)
 
625
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
 
626
            gnutls.handshake_set_private_extensions(self._c_object,
 
627
                                                    True)
653
628
            self.socket = socket
654
629
            if credentials is None:
655
630
                credentials = gnutls.Credentials()
656
 
            gnutls.credentials_set(self, credentials.type,
657
 
                                   credentials)
 
631
            gnutls.credentials_set(self._c_object, credentials.type,
 
632
                                   ctypes.cast(credentials._c_object,
 
633
                                               ctypes.c_void_p))
658
634
            self.credentials = credentials
659
635
 
660
636
        def __del__(self):
661
 
            gnutls.deinit(self)
 
637
            gnutls.deinit(self._c_object)
662
638
 
663
639
        def handshake(self):
664
 
            return gnutls.handshake(self)
 
640
            return gnutls.handshake(self._c_object)
665
641
 
666
642
        def send(self, data):
667
643
            data = bytes(data)
668
644
            data_len = len(data)
669
645
            while data_len > 0:
670
 
                data_len -= gnutls.record_send(self, data[-data_len:],
 
646
                data_len -= gnutls.record_send(self._c_object,
 
647
                                               data[-data_len:],
671
648
                                               data_len)
672
649
 
673
650
        def bye(self):
674
 
            return gnutls.bye(self, gnutls.SHUT_RDWR)
 
651
            return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
675
652
 
676
653
    # Error handling functions
677
654
    def _error_code(result):
678
655
        """A function to raise exceptions on errors, suitable
679
656
        for the 'restype' attribute on ctypes functions"""
680
 
        if result >= gnutls.E_SUCCESS:
 
657
        if result >= 0:
681
658
            return result
682
659
        if result == gnutls.E_NO_CERTIFICATE_FOUND:
683
660
            raise gnutls.CertificateSecurityError(code=result)
684
661
        raise gnutls.Error(code=result)
685
662
 
686
 
    def _retry_on_error(result, func, arguments,
687
 
                        _error_code=_error_code):
 
663
    def _retry_on_error(result, func, arguments):
688
664
        """A function to retry on some errors, suitable
689
665
        for the 'errcheck' attribute on ctypes functions"""
690
 
        while result < gnutls.E_SUCCESS:
 
666
        while result < 0:
691
667
            if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
692
668
                return _error_code(result)
693
669
            result = func(*arguments)
698
674
 
699
675
    # Functions
700
676
    priority_set_direct = _library.gnutls_priority_set_direct
701
 
    priority_set_direct.argtypes = [ClientSession, ctypes.c_char_p,
 
677
    priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
702
678
                                    ctypes.POINTER(ctypes.c_char_p)]
703
679
    priority_set_direct.restype = _error_code
704
680
 
705
681
    init = _library.gnutls_init
706
 
    init.argtypes = [PointerTo(ClientSession), ctypes.c_int]
 
682
    init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
707
683
    init.restype = _error_code
708
684
 
709
685
    set_default_priority = _library.gnutls_set_default_priority
710
 
    set_default_priority.argtypes = [ClientSession]
 
686
    set_default_priority.argtypes = [session_t]
711
687
    set_default_priority.restype = _error_code
712
688
 
713
689
    record_send = _library.gnutls_record_send
714
 
    record_send.argtypes = [ClientSession, ctypes.c_void_p,
 
690
    record_send.argtypes = [session_t, ctypes.c_void_p,
715
691
                            ctypes.c_size_t]
716
692
    record_send.restype = ctypes.c_ssize_t
717
693
    record_send.errcheck = _retry_on_error
719
695
    certificate_allocate_credentials = (
720
696
        _library.gnutls_certificate_allocate_credentials)
721
697
    certificate_allocate_credentials.argtypes = [
722
 
        PointerTo(Credentials)]
 
698
        ctypes.POINTER(certificate_credentials_t)]
723
699
    certificate_allocate_credentials.restype = _error_code
724
700
 
725
701
    certificate_free_credentials = (
726
702
        _library.gnutls_certificate_free_credentials)
727
 
    certificate_free_credentials.argtypes = [Credentials]
 
703
    certificate_free_credentials.argtypes = [
 
704
        certificate_credentials_t]
728
705
    certificate_free_credentials.restype = None
729
706
 
730
707
    handshake_set_private_extensions = (
731
708
        _library.gnutls_handshake_set_private_extensions)
732
 
    handshake_set_private_extensions.argtypes = [ClientSession,
 
709
    handshake_set_private_extensions.argtypes = [session_t,
733
710
                                                 ctypes.c_int]
734
711
    handshake_set_private_extensions.restype = None
735
712
 
736
713
    credentials_set = _library.gnutls_credentials_set
737
 
    credentials_set.argtypes = [ClientSession, credentials_type_t,
738
 
                                CastToVoidPointer(Credentials)]
 
714
    credentials_set.argtypes = [session_t, credentials_type_t,
 
715
                                ctypes.c_void_p]
739
716
    credentials_set.restype = _error_code
740
717
 
741
718
    strerror = _library.gnutls_strerror
743
720
    strerror.restype = ctypes.c_char_p
744
721
 
745
722
    certificate_type_get = _library.gnutls_certificate_type_get
746
 
    certificate_type_get.argtypes = [ClientSession]
 
723
    certificate_type_get.argtypes = [session_t]
747
724
    certificate_type_get.restype = _error_code
748
725
 
749
726
    certificate_get_peers = _library.gnutls_certificate_get_peers
750
 
    certificate_get_peers.argtypes = [ClientSession,
 
727
    certificate_get_peers.argtypes = [session_t,
751
728
                                      ctypes.POINTER(ctypes.c_uint)]
752
729
    certificate_get_peers.restype = ctypes.POINTER(datum_t)
753
730
 
760
737
    global_set_log_function.restype = None
761
738
 
762
739
    deinit = _library.gnutls_deinit
763
 
    deinit.argtypes = [ClientSession]
 
740
    deinit.argtypes = [session_t]
764
741
    deinit.restype = None
765
742
 
766
743
    handshake = _library.gnutls_handshake
767
 
    handshake.argtypes = [ClientSession]
768
 
    handshake.restype = ctypes.c_int
 
744
    handshake.argtypes = [session_t]
 
745
    handshake.restype = _error_code
769
746
    handshake.errcheck = _retry_on_error
770
747
 
771
748
    transport_set_ptr = _library.gnutls_transport_set_ptr
772
 
    transport_set_ptr.argtypes = [ClientSession, transport_ptr_t]
 
749
    transport_set_ptr.argtypes = [session_t, transport_ptr_t]
773
750
    transport_set_ptr.restype = None
774
751
 
775
752
    bye = _library.gnutls_bye
776
 
    bye.argtypes = [ClientSession, close_request_t]
777
 
    bye.restype = ctypes.c_int
 
753
    bye.argtypes = [session_t, close_request_t]
 
754
    bye.restype = _error_code
778
755
    bye.errcheck = _retry_on_error
779
756
 
780
757
    check_version = _library.gnutls_check_version
797
774
 
798
775
        x509_crt_fmt_t = ctypes.c_int
799
776
 
800
 
        # All the function declarations below are from
801
 
        # gnutls/abstract.h
 
777
        # All the function declarations below are from gnutls/abstract.h
802
778
        pubkey_init = _library.gnutls_pubkey_init
803
779
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
804
780
        pubkey_init.restype = _error_code
818
794
        pubkey_deinit.argtypes = [pubkey_t]
819
795
        pubkey_deinit.restype = None
820
796
    else:
821
 
        # All the function declarations below are from
822
 
        # gnutls/openpgp.h
 
797
        # All the function declarations below are from gnutls/openpgp.h
823
798
 
824
799
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
825
800
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
831
806
                                       openpgp_crt_fmt_t]
832
807
        openpgp_crt_import.restype = _error_code
833
808
 
834
 
        openpgp_crt_verify_self = \
835
 
            _library.gnutls_openpgp_crt_verify_self
836
 
        openpgp_crt_verify_self.argtypes = [
837
 
            openpgp_crt_t,
838
 
            ctypes.c_uint,
839
 
            ctypes.POINTER(ctypes.c_uint),
840
 
        ]
 
809
        openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
 
810
        openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
 
811
                                            ctypes.POINTER(ctypes.c_uint)]
841
812
        openpgp_crt_verify_self.restype = _error_code
842
813
 
843
814
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
854
825
 
855
826
    if check_version(b"3.6.4"):
856
827
        certificate_type_get2 = _library.gnutls_certificate_type_get2
857
 
        certificate_type_get2.argtypes = [ClientSession, ctypes.c_int]
 
828
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
858
829
        certificate_type_get2.restype = _error_code
859
830
 
860
831
    # Remove non-public functions
2320
2291
            priority = self.server.gnutls_priority
2321
2292
            if priority is None:
2322
2293
                priority = "NORMAL"
2323
 
            gnutls.priority_set_direct(session,
2324
 
                                       priority.encode("utf-8"), None)
 
2294
            gnutls.priority_set_direct(session._c_object,
 
2295
                                       priority.encode("utf-8"),
 
2296
                                       None)
2325
2297
 
2326
2298
            # Start communication using the Mandos protocol
2327
2299
            # Get protocol number
2354
2326
                    except (TypeError, gnutls.Error) as error:
2355
2327
                        logger.warning("Bad certificate: %s", error)
2356
2328
                        return
2357
 
                    logger.debug("Key ID: %s",
2358
 
                                 key_id.decode("utf-8",
2359
 
                                               errors="replace"))
 
2329
                    logger.debug("Key ID: %s", key_id)
2360
2330
 
2361
2331
                else:
2362
2332
                    key_id = b""
2454
2424
    def peer_certificate(session):
2455
2425
        "Return the peer's certificate as a bytestring"
2456
2426
        try:
2457
 
            cert_type = gnutls.certificate_type_get2(
2458
 
                session, gnutls.CTYPE_PEERS)
 
2427
            cert_type = gnutls.certificate_type_get2(session._c_object,
 
2428
                                                     gnutls.CTYPE_PEERS)
2459
2429
        except AttributeError:
2460
 
            cert_type = gnutls.certificate_type_get(session)
 
2430
            cert_type = gnutls.certificate_type_get(session._c_object)
2461
2431
        if gnutls.has_rawpk:
2462
2432
            valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
2463
2433
        else:
2470
2440
            return b""
2471
2441
        list_size = ctypes.c_uint(1)
2472
2442
        cert_list = (gnutls.certificate_get_peers
2473
 
                     (session, ctypes.byref(list_size)))
 
2443
                     (session._c_object, ctypes.byref(list_size)))
2474
2444
        if not bool(cert_list) and list_size.value != 0:
2475
2445
            raise gnutls.Error("error getting peer certificate")
2476
2446
        if list_size.value == 0:
2498
2468
        buf = ctypes.create_string_buffer(32)
2499
2469
        buf_len = ctypes.c_size_t(len(buf))
2500
2470
        # Get the key ID from the raw public key into the buffer
2501
 
        gnutls.pubkey_get_key_id(
2502
 
            pubkey,
2503
 
            gnutls.KEYID_USE_SHA256,
2504
 
            ctypes.cast(ctypes.byref(buf),
2505
 
                        ctypes.POINTER(ctypes.c_ubyte)),
2506
 
            ctypes.byref(buf_len))
 
2471
        gnutls.pubkey_get_key_id(pubkey,
 
2472
                                 gnutls.KEYID_USE_SHA256,
 
2473
                                 ctypes.cast(ctypes.byref(buf),
 
2474
                                             ctypes.POINTER(ctypes.c_ubyte)),
 
2475
                                 ctypes.byref(buf_len))
2507
2476
        # Deinit the certificate
2508
2477
        gnutls.pubkey_deinit(pubkey)
2509
2478
 
2754
2723
            address = request[3]
2755
2724
 
2756
2725
            for c in self.clients.values():
2757
 
                if key_id == ("E3B0C44298FC1C149AFBF4C8996FB924"
2758
 
                              "27AE41E4649B934CA495991B7852B855"):
 
2726
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
2759
2727
                    continue
2760
2728
                if key_id and c.key_id == key_id:
2761
2729
                    client = c
2813
2781
def rfc3339_duration_to_delta(duration):
2814
2782
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2815
2783
 
2816
 
    >>> timedelta = datetime.timedelta
2817
 
    >>> rfc3339_duration_to_delta("P7D") == timedelta(7)
2818
 
    True
2819
 
    >>> rfc3339_duration_to_delta("PT60S") == timedelta(0, 60)
2820
 
    True
2821
 
    >>> rfc3339_duration_to_delta("PT60M") == timedelta(0, 3600)
2822
 
    True
2823
 
    >>> rfc3339_duration_to_delta("PT24H") == timedelta(1)
2824
 
    True
2825
 
    >>> rfc3339_duration_to_delta("P1W") == timedelta(7)
2826
 
    True
2827
 
    >>> rfc3339_duration_to_delta("PT5M30S") == timedelta(0, 330)
2828
 
    True
2829
 
    >>> rfc3339_duration_to_delta("P1DT3M20S") == timedelta(1, 200)
2830
 
    True
2831
 
    >>> del timedelta
 
2784
    >>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7)
 
2785
    True
 
2786
    >>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60)
 
2787
    True
 
2788
    >>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(0, 3600)
 
2789
    True
 
2790
    >>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1)
 
2791
    True
 
2792
    >>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7)
 
2793
    True
 
2794
    >>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330)
 
2795
    True
 
2796
    >>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200)
 
2797
    True
2832
2798
    """
2833
2799
 
2834
2800
    # Parsing an RFC 3339 duration with regular expressions is not
3201
3167
 
3202
3168
        @gnutls.log_func
3203
3169
        def debug_gnutls(level, string):
3204
 
            logger.debug("GnuTLS: %s",
3205
 
                         string[:-1].decode("utf-8",
3206
 
                                            errors="replace"))
 
3170
            logger.debug("GnuTLS: %s", string[:-1])
3207
3171
 
3208
3172
        gnutls.global_set_log_function(debug_gnutls)
3209
3173