Bug 1937764
| Summary: | free of bad pointer in XS_GSSAPI__OID_DESTROY | ||||||
|---|---|---|---|---|---|---|---|
| Product: | Red Hat Enterprise Linux 8 | Reporter: | Paulo Andrade <pandrade> | ||||
| Component: | perl-GSSAPI | Assignee: | Jitka Plesnikova <jplesnik> | ||||
| Status: | CLOSED ERRATA | QA Contact: | Martin Kyral <mkyral> | ||||
| Severity: | medium | Docs Contact: | |||||
| Priority: | medium | ||||||
| Version: | 8.3 | CC: | amike, jplesnik, mkyral, mspacek, payerle, ppisar | ||||
| Target Milestone: | rc | Keywords: | Triaged | ||||
| Target Release: | --- | Flags: | pm-rhel:
mirror+
|
||||
| Hardware: | All | ||||||
| OS: | Linux | ||||||
| Whiteboard: | |||||||
| Fixed In Version: | perl-GSSAPI-0.28-25.el8 | Doc Type: | If docs needed, set a value | ||||
| Doc Text: | Story Points: | --- | |||||
| Clone Of: | |||||||
| : | 1994263 (view as bug list) | Environment: | |||||
| Last Closed: | 2021-11-09 18:52:05 UTC | Type: | Bug | ||||
| Regression: | --- | Mount Type: | --- | ||||
| Documentation: | --- | CRM: | |||||
| Verified Versions: | Category: | --- | |||||
| oVirt Team: | --- | RHEL 7.3 requirements from Atomic Host: | |||||
| Cloudforms Team: | --- | Target Upstream Version: | |||||
| Embargoed: | |||||||
| Attachments: |
|
||||||
Thanks for the detailed bug report. It looks like a duplicate of bug #1932477. Closest somewhat related information I could find around is https://sourceforge.net/p/perlgssapi/mailman/perlgssapi-users/thread/200610051906.51257.achim%40grolmsnet.de/ BTW, my suggested pseudo patch is wrong, it is not a list of pointers, but a non null terminated string, so, a fix would not require multiplying the length by sizeof(void*). I confirm this issue with perl-GSSAPI-0.28-23.el8.x86_64 and krb5-libs-1.18.2-8.el8.x86_64. *** Bug 1932477 has been marked as a duplicate of this bug. *** I also came to the same conlusion that libgssapi_krb5.so copies gss_krb5_mechanism_oid_desc content into an output OID and then when asked to free it with gss_release_oid(), the library does not recognize as a static (because of a use-supplied pointer not matching any list in generic_gss_release_oid(), and attempts to free() it. The patch is definitely wrong because gssint_get_mech_type(), where the affected OID is built, calls gssint_get_mech_type_oid() in general case. And gssint_get_mech_type_oid() also does not make deep copies. It cannot because the OID argument is not a pointer to pointer to allocate a fresh new struct gss_OID. I found a related bug report at perl-GSSAPI upstream's bug tracker <https://rt.cpan.org/Public/Bug/Display.html?id=121873> which points to GSSAPI specification which reads that a mech_type 7th argument of agss_accept_sec_context() returns pointers to a static storage. I.e. libgssapi_krb5.so behaves correctly and it's a fault of Perl GSSAPI. The Perl module should exempt the 6th argument (and maybe others as well) of GSSAPI::Context::accept() from calls to gss_release_oid() in GSSAPI::OID destructor. A workaround for the user is pass undef to GSSAPI::Context::accept() calls:
my $status = GSSAPI::Context::accept(
$server_context,
GSS_C_NO_CREDENTIAL,
$gss_input_token,
GSS_C_NO_CHANNEL_BINDINGS,
my $gss_client_name,
→ undef # my $out_mech,
my $gss_output_token,
my $out_flags,
my $out_time,
my $gss_delegated_cred);
Thanks for the information. I guessed the patch was incorrect, and would need a full review of other uses around, including in krb5 itself, as you have shown would be wrong. It was just looking at what the perl module did expect, and I knew it likely would at least possibly create a leak on other code, that did not call generic_gss_release_oid because it was not supposed to, or to workaround a possible bug. Don't worry. I will come up with a patch. I will review how GSSAPI::OID object handle modifications and probably end up with making a deep copy in GSSAPI::Context::accept(). Summary:
Issue is in GSSAPI::OID in situation, when object is destroying and content is not destructible
From krb5-1.19.2/src/lib/gssapi/mechglue/g_glue.c:
static gss_OID_desc gss_krb5_mechanism_oid_desc =
{9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
which goes to GSSAPI::OID.
In GSSAPI::OID is check for this type of content and this check isn't working.
Original code:
From GSSAPI-0.28/xs/OID.xs:
if (oid != NULL &&
oid != __KRB5_MECHTYPE_OID &&
oid != __KRB5_OLD_MECHTYPE_OID &&
oid != __GSS_KRB5_NT_USER_NAME &&
oid != __GSS_KRB5_NT_PRINCIPAL_NAME &&
oid != __SPNEGO_MECHTYPE_OID &&
oid != __gss_mech_krb5_v2 ) {
(void)gss_release_oid(&minor, &oid);
}
__KRB5_MECHTYPE_OID is what we need to check.
From GSSAPI-0.28/GSSAPI.xs:
#define __KRB5_MECHTYPE_OID &mygss_mech_krb5
...
static gss_OID_desc mygss_mech_krb5 = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
We need to change this check to functional version.
if (oid != NULL &&
! gss_oid_equal(oid, __KRB5_MECHTYPE_OID) &&
! gss_oid_equal(oid, __KRB5_OLD_MECHTYPE_OID) &&
! gss_oid_equal(oid, __GSS_KRB5_NT_USER_NAME) &&
! gss_oid_equal(oid, __GSS_KRB5_NT_PRINCIPAL_NAME) &&
! gss_oid_equal(oid, __SPNEGO_MECHTYPE_OID) &&
! gss_oid_equal(oid, __gss_mech_krb5_v2) ) {
(void)gss_release_oid(&minor, &oid);
}
Patch in attachment.
This patch is wrong. It leaks a memory if a value of an OID object accidentally matches one of the 6 exceptions. Example:
$ perl -MGSSAPI -e 'while (1) { GSSAPI::OID->from_str(GSSAPI::OID->new, gss_mech_krb5) }'
You can replace gss_mech_krb5 with "{ 1 2 840 113554 1 2 2 }" string to make this leaking reproducer more obvious.
Created attachment 1815698 [details]
A different fix with copying the objects
This fix takes a different approach and does not suffer from the memory leaks.
I have to reopen the bug due to regression. Since the problem described in this bug report should be resolved in a recent advisory, it has been closed with a resolution of ERRATA. For information on the advisory (perl-GSSAPI bug fix and enhancement update), and where to find the updated files, follow the link below. If the solution does not work for you, open a new bug report. https://access.redhat.com/errata/RHBA-2021:4327 |
This issue is somewhat complex, and the proper fix could be required to be done in krb5-source-code/lib/gssapi/generic/oid_ops.c From gdb: Core was generated by `/bin/perl /usr/share/doc/perl-GSSAPI/examples/gss-server.pl --port=... --keyta'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00007f27f3d48b23 in __GI___libc_free (mem=<optimized out>) at malloc.c:3131 3131 ar_ptr = arena_for_chunk (p); (gdb) bt #0 0x00007f27f3d48b23 in __GI___libc_free (mem=<optimized out>) at malloc.c:3131 #1 0x00007f27f2fe17c6 in generic_gss_release_oid ( minor_status=minor_status@entry=0x7fffc750333c, oid=oid@entry=0x7fffc7503340) at oid_ops.c:102 #2 0x00007f27f2fee6df in gss_release_oid ( minor_status=minor_status@entry=0x7fffc750333c, oid=oid@entry=0x7fffc7503340) at g_initialize.c:202 #3 0x00007f27f322f5cf in XS_GSSAPI__OID_DESTROY (my_perl=<optimized out>, cv=0x564037c87130) at ./xs/OID.xs:24 #4 0x00007f27f4f58149 in Perl_pp_entersub (my_perl=0x5640378d42a0) at pp_hot.c:4227 In rhel7 it just shows something like this: *** Error in `perl': munmap_chunk(): invalid pointer: 0x00007f6cb22122be *** ======= Backtrace: ========= /lib64/libc.so.6(+0x7f3e4)[0x7f6cb940b3e4] /lib64/libgssapi_krb5.so.2(+0xd5a2)[0x7f6cb21df5a2] /usr/lib64/perl5/vendor_perl/auto/GSSAPI/GSSAPI.so(+0xc13c)[0x7f6cb242b13c] /usr/lib64/perl5/CORE/libperl.so(Perl_pp_entersub+0x58f)[0x7f6cba7a642f] /usr/lib64/perl5/CORE/libperl.so(Perl_call_sv+0x69d)[0x7f6cba73640d] /usr/lib64/perl5/CORE/libperl.so(+0xc5185)[0x7f6cba7af185] ... Explaining better the issue: OID.xs has this: """ void DESTROY(oid) GSSAPI::OID oid PREINIT: OM_uint32 minor; PPCODE: #if !defined(HEIMDAL) if (oid != NULL && oid != __KRB5_MECHTYPE_OID && oid != __KRB5_OLD_MECHTYPE_OID && oid != __GSS_KRB5_NT_USER_NAME && oid != __GSS_KRB5_NT_PRINCIPAL_NAME && oid != __SPNEGO_MECHTYPE_OID && oid != __gss_mech_krb5_v2 ) { (void)gss_release_oid(&minor, &oid); } #endif #if defined(HEIMDAL) # warn("gss_release_oid is unsupported and not Part of the API!"); #endif """ and in GSSAPI.xs we see: """ #define __KRB5_MECHTYPE_OID &mygss_mech_krb5 #define __KRB5_OLD_MECHTYPE_OID &mygss_mech_krb5_old #define __SPNEGO_MECHTYPE_OID &myspnego_oid_desc #define __GSS_KRB5_NT_USER_NAME &mygss_nt_krb5_name #define __GSS_KRB5_NT_PRINCIPAL_NAME &mygss_nt_krb5_principal #define __gss_mech_krb5_v2 &mygss_mech_krb5_v2 ... static gss_OID_desc mygss_mech_krb5 = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"}; static gss_OID_desc mygss_mech_krb5_old = {5, (void *) "\x2b\x05\x01\x05\x02"}; static gss_OID_desc myspnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"}; static gss_OID_desc mygss_nt_krb5_name = {10, (void *) "\052\206\110\206\367\022\001\002\002\001"}; static gss_OID_desc mygss_nt_krb5_principal = {10, (void *) "\052\206\110\206\367\022\001\002\002\002"}; static gss_OID_desc mygss_mech_krb5_v2 = {9, (void *) "\052\206\110\206\367\022\001\002\003"}; """ so, it already checks for several possible bad free calls. Valgrind did show a hint of the problem: """ ==582529== Invalid free() / delete / delete[] / realloc() ==582529== at 0x4C320DC: free (vg_replace_malloc.c:540) ==582529== by 0x7AA77C5: generic_gss_release_oid (oid_ops.c:102) ==582529== by 0x78905CE: XS_GSSAPI__OID_DESTROY (OID.xs:24) ==582529== by 0x4F27148: Perl_pp_entersub (pp_hot.c:4227) ==582529== by 0x4E96FDD: Perl_call_sv (perl.c:2856) ==582529== by 0x4F2C382: S_curse (sv.c:6974) ==582529== by 0x4F2CB8F: Perl_sv_clear (sv.c:6578) ==582529== by 0x4F2D101: Perl_sv_free2 (sv.c:7075) ==582529== by 0x4F5EC50: Perl_leave_scope (scope.c:1178) ==582529== by 0x4F6AEA2: Perl_pp_leave (pp_ctl.c:2121) ==582529== by 0x4F1EF94: Perl_runops_standard (run.c:41) ==582529== by 0x4E9EFAE: S_run_body (perl.c:2532) ==582529== by 0x4E9EFAE: perl_run (perl.c:2455) ==582529== Address 0x7adb6c6 is in a r-x mapped file /usr/lib64/libgssapi_krb5.so.2.2 segment """ In the coredump we see: """ (gdb) f 1 #1 0x00007f27f2fe17c6 in generic_gss_release_oid ( minor_status=minor_status@entry=0x7fffc750333c, oid=oid@entry=0x7fffc7503340) at oid_ops.c:102 102 free((*oid)->elements); (gdb) p oid $1 = (gss_OID *) 0x7fffc7503340 (gdb) p *oid $2 = (gss_OID) 0x564037cbadb0 (gdb) p **oid $3 = {length = 9, elements = 0x7f27f30156c6} """ and with some trial&error inspecting source code what was found is: """ (gdb) p gss_krb5_mechanism_oid_desc $4 = {length = 9, elements = 0x7f27f30156c6} (gdb) p oid.elements == gss_krb5_mechanism_oid_desc.elements $5 = 1 """ and the culprit should be: """ lib/gssapi/mechglue/g_glue.c:284 281 } else if (token->length != 0 && 282 ((char *)token->value)[0] == 0x6E) { 283 /* Could be a raw AP-REQ (check for APPLICATION tag) */ 284 *OID = gss_krb5_mechanism_oid_desc; 285 } else if (token->length == 0) { 286 *OID = gss_spnego_mechanism_oid_desc; """ generic_gss_release_oid() already has a check similar to the XS code, but does not check for gss_krb5_mechanism_oid_desc. Even if did the check it would be pointless, as it is doing a struct copy and not passing around a reference to it (gss_krb5_mechanism_oid_desc). Not knowing the code and/or possible side effects, a possible fix would be in krb5 code, like replace lib/gssapi/mechglue/g_glue.c:284 from: *OID = gss_krb5_mechanism_oid_desc; with something like: *OID->length = gss_krb5_mechanism_oid_desc.length; *OID->elements = malloc(sizeof(void*) * gss_krb5_mechanism_oid_desc.length); memcpy(*OID->elements, gss_krb5_mechanism_oid_desc.elements, sizeof(void*) * gss_krb5_mechanism_oid_desc.length); and do the same for other static variables where *OID is set. This should fix the perl-GSSAPI issue, but might cause problems elsewhere, at least a leak on any code that might not be calling gss_release_oid to circumvent the problem. Feel free to reassign the issue to krb5 component if it is more applicable.