Bug 1699062 - Segfault in libvirt's virNetTLSContextNew function when gnutls library is in LIB_STATE_ERROR state
Summary: Segfault in libvirt's virNetTLSContextNew function when gnutls library is in ...
Keywords:
Status: CLOSED UPSTREAM
Alias: None
Product: Virtualization Tools
Classification: Community
Component: libvirt
Version: unspecified
Hardware: x86_64
OS: Linux
unspecified
medium
Target Milestone: ---
Assignee: Libvirt Maintainers
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2019-04-11 16:47 UTC by Adrian Brzezinski
Modified: 2019-04-16 12:53 UTC (History)
6 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed: 2019-04-16 12:53:36 UTC
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
Foreman Issue Tracker 26574 0 'Normal' 'New' 'Segmentation Fault - unprooper file descritpor handling (Foreman, Katello, Dynflow or others)' 2019-11-20 19:14:18 UTC

Description Adrian Brzezinski 2019-04-11 16:47:51 UTC
Description of problem:

When gnutls is in LIB_STATE_ERROR, it means that further memory allocations will fail.
virNetTLSContextNew function in src/rpc/virnettlscontext.c file doesn't take into account that case and attempt to free already freed ctxt->x509cred structure, which unavoidably leads to segfault.

It was discovered after analysis of frequent dynflowd service segfaults in Foreman. On the foreman's side it has been reported and described here: https://projects.theforeman.org/issues/26574

Version-Release number of selected component (if applicable):

Bug was introduced in version 0.9.3 and is present in all subsequent versions
It may be attributed to this commit from year 2010:

https://github.com/libvirt/libvirt/commit/30fd0bbbfc76469dee5f636d3f8972c81ff43f85

How reproducible:

In foreman 1.21 apply errata on systems that are running as QEMU-KVM/LibVirt guests.

Steps to Reproduce:
1. Log in to foreman
2. Navigate to Hosts/Content Hosts
3. Select from the list any host with installable updates and select "Manage Errata" from "Select Action" drop down menu
4. Select and install some updates

Actual results:

Lets examine it through tour with gdb session. dynflow_executor can't read entropy from /dev/urandom which causes gnutls to switch into LIB_STATE_ERROR state:

(gdb) handle SIGPIPE nostop noprint pass
Signal        Stop      Print   Pass to program Description
SIGPIPE       No        No      Yes             Broken pipe
(gdb) break rpc/virnettlscontext.c:710 if ret != 0
No symbol "ret" in current context.
(gdb) break rpc/virnettlscontext.c:760
Breakpoint 1 at 0x7f884e656380: file rpc/virnettlscontext.c, line 760.
(gdb) break gnutls_cert.c:230 if ret != 0
Breakpoint 2 at 0x7f884c697d3a: file gnutls_cert.c, line 230.
(gdb) watch _gnutls_lib_mode
Hardware watchpoint 3: _gnutls_lib_mode
(gdb) continue
Continuing.
...
[Switching to Thread 0x7f882bdfd700 (LWP 15300)]
Hardware watchpoint 3: _gnutls_lib_mode

Old value = 3
New value = 4
rnd_func (_ctx=<optimized out>, length=<optimized out>, data=<optimized out>) at pk.c:69
69      }
(gdb) info symbol rnd_func
rnd_func in section .text of /lib64/libgnutls.so.28
(gdb) list
64                      _gnutls_switch_lib_state(LIB_STATE_ERROR);
65      #else
66                      abort();
67      #endif
68              }
69      }
70
71      static void
72      ecc_scalar_zclear (struct ecc_scalar *s)
73      {
(gdb) bt full                                                                                                                                                                                                                                                        [1/9284]
#0  rnd_func (_ctx=<optimized out>, length=<optimized out>, data=<optimized out>) at pk.c:69
No locals.
#1  0x00007f884af6a55a in nettle_mpz_random_size () from /lib64/libhogweed.so.2
No symbol table info available.
#2  0x00007f884af6a5e4 in nettle_mpz_random () from /lib64/libhogweed.so.2
No symbol table info available.
#3  0x00007f884af6cf09 in _nettle_rsa_blind () from /lib64/libhogweed.so.2
No symbol table info available.
#4  0x00007f884af6bc91 in nettle_rsa_pkcs1_sign_tr () from /lib64/libhogweed.so.2
No symbol table info available.
#5  0x00007f884c740849 in _wrap_nettle_pk_sign (algo=<optimized out>, signature=0x7f882bdf1eb0, vdata=0x7f882bdf1e40, pk_params=0x7f8820f911a0) at pk.c:651
        priv = {size = 512, d = {{_mp_alloc = 65, _mp_size = 64, _mp_d = 0x7f8820fa1fd0}}, p = {{_mp_alloc = 33, _mp_size = 32, _mp_d = 0x7f8820f92670}}, q = {{_mp_alloc = 33, _mp_size = 32, _mp_d = 0x7f8820fa21e0}}, a = {{_mp_alloc = 32, _mp_size = 32,
              _mp_d = 0x7f8820fa2400}}, b = {{_mp_alloc = 32, _mp_size = 32, _mp_d = 0x7f8820fa2510}}, c = {{_mp_alloc = 32, _mp_size = 32, _mp_d = 0x7f8820fa22f0}}}
        pub = {size = 512, n = {{_mp_alloc = 65, _mp_size = 64, _mp_d = 0x7f8820fa1dc0}}, e = {{_mp_alloc = 1, _mp_size = 1, _mp_d = 0x7f8820f93180}}}
        s = {{_mp_alloc = 64, _mp_size = 64, _mp_d = 0x7f8820fa5b30}}
        ret = <optimized out>
        hash_len = 0
        me = <optimized out>
#6  0x00007f884c6a6dd7 in gnutls_privkey_sign_data (signer=0x7f8820f984c0, hash=hash@entry=GNUTLS_DIG_SHA256, flags=flags@entry=0, data=data@entry=0x7f882bdf1ea0, signature=signature@entry=0x7f882bdf1eb0) at gnutls_privkey.c:796
        ret = 0
        digest = {data = 0x7f8820fa1b60 "010\r\006\t`\206H\001e\003\004\002\001\005", size = 51}
        me = <optimized out>
#7  0x00007f884c698655 in _gnutls_check_key_cert_match (res=res@entry=0x7f8820f8afd0) at gnutls_cert.c:947
        test = {data = 0x7f884c74cd90 "test text", size = 9}
        sig = {data = 0x0, size = 0}
        pk = 1
        pk2 = <optimized out>
        ret = <optimized out>
        __func__ = "_gnutls_check_key_cert_match"
#8  0x00007f884c6a4944 in gnutls_certificate_set_x509_key_file2 (res=0x7f8820f8afd0, certfile=<optimized out>, certfile@entry=0x7f8820f9c970 "/etc/pki/libvirt/clientcert.pem", keyfile=keyfile@entry=0x7f8820f9c8d0 "/etc/pki/libvirt/private/clientkey.pem",
    type=type@entry=GNUTLS_X509_FMT_PEM, pass=pass@entry=0x0, flags=flags@entry=0) at gnutls_x509.c:1410
        ret = <optimized out>
#9  0x00007f884c6a4ebb in gnutls_certificate_set_x509_key_file (res=<optimized out>, certfile=certfile@entry=0x7f8820f9c970 "/etc/pki/libvirt/clientcert.pem", keyfile=keyfile@entry=0x7f8820f9c8d0 "/etc/pki/libvirt/private/clientkey.pem",
    type=type@entry=GNUTLS_X509_FMT_PEM) at gnutls_x509.c:1350
No locals.
#10 0x00007f884e656a92 in virNetTLSContextLoadCredentials (ctxt=0x7f8820f8ae10, key=0x7f8820f9c8d0 "/etc/pki/libvirt/private/clientkey.pem", cert=0x7f8820f9c970 "/etc/pki/libvirt/clientcert.pem", cacrl=0x7f8820f9bfe0 "/etc/pki/CA/cacrl.pem",
    cacert=0x7f8820f9c750 "/etc/pki/CA/cacert.pem", isServer=false) at rpc/virnettlscontext.c:664
        rv = <optimized out>
        ret = -1
        err = <optimized out>
#11 virNetTLSContextNew (cacert=0x7f8820f9c750 "/etc/pki/CA/cacert.pem", cacrl=0x7f8820f9bfe0 "/etc/pki/CA/cacrl.pem", cert=0x7f8820f9c970 "/etc/pki/libvirt/clientcert.pem", key=0x7f8820f9c8d0 "/etc/pki/libvirt/private/clientkey.pem",
    x509dnWhitelist=x509dnWhitelist@entry=0x0, priority=priority@entry=0x0, sanityCheckCert=sanityCheckCert@entry=true, requireValidCert=requireValidCert@entry=true, isServer=isServer@entry=false) at rpc/virnettlscontext.c:721
        ctxt = 0x7f8820f8ae10
        err = <optimized out>
        __FUNCTION__ = "virNetTLSContextNew"
        __func__ = "virNetTLSContextNew"
...

Further code execution will leads to gnutls failed memory allocation with GNUTLS_E_LIB_IN_ERROR_STATE error.
As a consequence of that structure is freed with with gnutls_free at line 232 and GNUTLS_E_MEMORY_ERROR
is being returned to the caller

(gdb) cont
Continuing
....
[Switching to Thread 0x7f8848260700 (LWP 15440)]

Breakpoint 2, gnutls_certificate_allocate_credentials (res=0x7f882c6aab88) at gnutls_cert.c:230
230             if (ret < 0) {
(gdb) info symbol gnutls_certificate_allocate_credentials
gnutls_certificate_allocate_credentials in section .text of /lib64/libgnutls.so.28
(gdb) print ret
$3 = -402
(gdb) list
225
226             if (*res == NULL)
227                     return GNUTLS_E_MEMORY_ERROR;
228
229             ret = gnutls_x509_trust_list_init(&(*res)->tlist, 0);
230             if (ret < 0) {
231                     gnutls_assert();
232                     gnutls_free(*res);
233                     return GNUTLS_E_MEMORY_ERROR;
234             }
(gdb) list verify-high.c:77
72      gnutls_x509_trust_list_init(gnutls_x509_trust_list_t * list,
73                                  unsigned int size)
74      {
75              gnutls_x509_trust_list_t tmp;
76
77              FAIL_IF_LIB_ERROR;
78
79              tmp =
80                  gnutls_calloc(1, sizeof(struct gnutls_x509_trust_list_st));
(gdb) bt
#0  gnutls_certificate_allocate_credentials (res=0x7f882c6aab88) at gnutls_cert.c:230
#1  0x00007f884e656134 in virNetTLSContextNew (cacert=0x7f882c6aa6f0 "/etc/pki/CA/cacert.pem", cacrl=0x7f882c6a7620 "/etc/pki/CA/cacrl.pem", cert=0x7f882c6aad90 "/etc/pki/libvirt/clientcert.pem", key=0x7f882c6aacf0 "/etc/pki/libvirt/private/clientkey.pem",
    x509dnWhitelist=x509dnWhitelist@entry=0x0, priority=priority@entry=0x0, sanityCheckCert=sanityCheckCert@entry=true, requireValidCert=requireValidCert@entry=true, isServer=isServer@entry=false) at rpc/virnettlscontext.c:709
#2  0x00007f884e6570d2 in virNetTLSContextNewPath (pkipath=<optimized out>, tryUserPkiPath=<optimized out>, x509dnWhitelist=x509dnWhitelist@entry=0x0, priority=priority@entry=0x0, sanityCheckCert=sanityCheckCert@entry=true,
    requireValidCert=requireValidCert@entry=true, isServer=isServer@entry=false) at rpc/virnettlscontext.c:908
#3  0x00007f884e657f61 in virNetTLSContextNewClientPath (pkipath=<optimized out>, tryUserPkiPath=<optimized out>, priority=priority@entry=0x0, sanityCheckCert=sanityCheckCert@entry=true, requireValidCert=requireValidCert@entry=true) at rpc/virnettlscontext.c:937
#4  0x00007f884e7068cb in doRemoteOpen (conn=conn@entry=0x7f882c6aa9a0, priv=0x7f882c6aac50, driver_str=0x7f882c6aae20 "qemu", transport_str=<optimized out>, auth=auth@entry=0x0, conf=conf@entry=0x0, flags=0) at remote/remote_driver.c:968
#5  0x00007f884e708436 in remoteConnectOpen (conn=0x7f882c6aa9a0, auth=0x0, conf=0x0, flags=<optimized out>) at remote/remote_driver.c:1385
#6  0x00007f884e718207 in virConnectOpenInternal (name=<optimized out>, name@entry=0x7f882c6aa4d0 "qemu+tls://blade4s15.hv.internal/system", auth=auth@entry=0x0, flags=flags@entry=0) at libvirt.c:1040
#7  0x00007f884e7199a9 in virConnectOpen (name=0x7f882c6aa4d0 "qemu+tls://blade4s15.hv.internal/system") at libvirt.c:1118

All preconditions are set, so further execution leads to segmentation fault

(gdb) cont
Continuing.

Breakpoint 1, virNetTLSContextNew (cacert=0x7f882c6aa6f0 "/etc/pki/CA/cacert.pem", cacrl=0x7f882c6a7620 "/etc/pki/CA/cacrl.pem", cert=0x7f882c6aad90 "/etc/pki/libvirt/clientcert.pem", key=0x7f882c6aacf0 "/etc/pki/libvirt/private/clientkey.pem",
    x509dnWhitelist=x509dnWhitelist@entry=0x0, priority=priority@entry=0x0, sanityCheckCert=sanityCheckCert@entry=true, requireValidCert=requireValidCert@entry=true, isServer=isServer@entry=false) at rpc/virnettlscontext.c:760
760         if (isServer)
(gdb) print ctxt
$4 = (virNetTLSContextPtr) 0x7f882c6aab50
(gdb) print *ctxt
$5 = {parent = {parent = {u = {dummy_align1 = 7700611093, dummy_align2 = 0x1cafe0015, s = {magic = 3405643797, refs = 1}}, klass = 0x7f8820f9c9a0}, lock = {lock = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0, __kind = 512, __spins = 0, __elision = 0,
          __list = {__prev = 0x0, __next = 0x0}}, __size = '\000' <repeats 17 times>, "\002", '\000' <repeats 21 times>, __align = 0}}}, x509cred = 0x7f882c6aaf20, dhParams = 0x0, isServer = false, requireValidCert = false, x509dnWhitelist = 0x0, priority = 0x0}
(gdb) print *ctxt->x509cred
$6 = {dh_params = 0x0, params_func = 0x39353a3132203830, certs = 0x2b3030362e34333a, ncerts = 808464432, pkey = 0x727265203a303434, keyring = 0x726976203a20726f, tlist = 0x6f43534c5474654e, verify_flags = 2019914862, verify_depth = 2003127924,
  verify_bits = 858863418, get_cert_callback = 0x6f7420656c62616e, client_get_cert_callback = 0x7461636f6c6c6120, server_get_cert_callback = 0x6320393035782065, get_cert_callback2 = 0x65646572, verify_callback = 0x141, pin = {cb = 0x7f882c000078,
    data = 0x7f882c000078}, pin_tmp = ":34.600+0000: 15440: error : vir", ocsp_func = 0x6f43534c5474654e, ocsp_func_ptr = 0x77654e747865746e, ocsp_response_file = 0x55203a203331373a <Address 0x55203a203331373a out of bounds>}
(gdb) print *ctxt->x509cred.tlist
Cannot access memory at address 0x6f43534c5474654e
(gdb) cont
Continuing.

Program received signal SIGSEGV, Segmentation fault.
gnutls_x509_trust_list_deinit (list=0x6f43534c5474654e, all=1) at verify-high.c:122
122             for (j = 0; j < list->blacklisted_size; j++) {

Expected results:

No segfaults :)

Additional info:

Lets take a closer look on virNetTLSContextNew function:

 686 static virNetTLSContextPtr virNetTLSContextNew(const char *cacert,
 687                                                const char *cacrl,
 688                                                const char *cert,
 689                                                const char *key,
 690                                                const char *const*x509dnWhitelist,
 691                                                const char *priority,
 692                                                bool sanityCheckCert,
 693                                                bool requireValidCert,
 694                                                bool isServer)
 695 {
 696     virNetTLSContextPtr ctxt;
 697     int err;
 698
 699     if (virNetTLSContextInitialize() < 0)
 700         return NULL;
 701
 702     if (!(ctxt = virObjectLockableNew(virNetTLSContextClass)))
 703         return NULL;
 704
 705     if (VIR_STRDUP(ctxt->priority, priority) < 0)
 706         goto error;
 707
 708     err = gnutls_certificate_allocate_credentials(&ctxt->x509cred);
 709     if (err) {
 710         virReportError(VIR_ERR_SYSTEM_ERROR,
 711                        _("Unable to allocate x509 credentials: %s"),
 712                        gnutls_strerror(err));
 713         goto error;
 714     }
 
 ....
 
 758  error:
 759     if (isServer)
 760         gnutls_dh_params_deinit(ctxt->dhParams);
 761     gnutls_certificate_free_credentials(ctxt->x509cred);
 762     VIR_FREE(ctxt);
 763     return NULL;
 764 } 

Here we can see that segfault would occur in case when allocation fails in lines 705 or 708.
Additionally ctxt->priority it't freed if error occures at a later stage of the code,
which can leads to memory leak.

Proposed patch:

diff --git a/src/rpc/virnettlscontext.c b/src/rpc/virnettlscontext.c
index 72e9ed9..8f6ec8f 100644
--- a/src/rpc/virnettlscontext.c
+++ b/src/rpc/virnettlscontext.c
@@ -703,14 +703,14 @@ static virNetTLSContextPtr virNetTLSContextNew(const char *cacert,
         return NULL;

     if (VIR_STRDUP(ctxt->priority, priority) < 0)
-        goto error;
+        goto ctxt_init_error;

     err = gnutls_certificate_allocate_credentials(&ctxt->x509cred);
     if (err) {
         virReportError(VIR_ERR_SYSTEM_ERROR,
                        _("Unable to allocate x509 credentials: %s"),
                        gnutls_strerror(err));
-        goto error;
+        goto ctxt_init_error;
     }

     if (sanityCheckCert &&
@@ -759,6 +759,8 @@ static virNetTLSContextPtr virNetTLSContextNew(const char *cacert,
     if (isServer)
         gnutls_dh_params_deinit(ctxt->dhParams);
     gnutls_certificate_free_credentials(ctxt->x509cred);
+ ctxt_init_error:
+    if (ctxt->priority) VIR_FREE(ctxt->priority);
     VIR_FREE(ctxt);
     return NULL;
 }

Comment 1 Ján Tomko 2019-04-12 11:23:11 UTC
libvirt patches are merged through the libvir-list mailing list
Would you like to propose the patch there?
See https://libvirt.org/hacking.html#patches
especially bullet #6 about the Sign-off

Comment 2 Adrian Brzezinski 2019-04-12 13:42:47 UTC
I followed Your advice and sended patch to libvir-list. Thanks


Note You need to log in before you can comment on or make changes to this bug.