Commit message: Fix double-free in SPNEGO [CVE-2014-4343]

In commit cd7d6b08 ("Verify acceptor's mech in SPNEGO initiator") the
pointer sc->internal_mech became an alias into sc->mech_set->elements,
which should be considered constant for the duration of the SPNEGO
context.  So don't free it.

CVE-2014-4343:

In MIT krb5 releases 1.10 and newer, an unauthenticated remote
attacker with the ability to spoof packets appearing to be from a
GSSAPI acceptor can cause a double-free condition in GSSAPI initiators
(clients) which are using the SPNEGO mechanism, by returning a
different underlying mechanism than was proposed by the initiator.  At
this stage of the negotiation, the acceptor is unauthenticated, and
the acceptor's response could be spoofed by an attacker with the
ability to inject traffic to the initiator.

Historically, some double-free vulnerabilities can be translated into
remote code execution, though the necessary exploits must be tailored
to the individual application and are usually quite
complicated. Double-frees can also be exploited to cause an
application crash, for a denial of service.  However, most GSSAPI
client applications are not vulnerable, as the SPNEGO mechanism is not
used by default (when GSS_C_NO_OID is passed as the mech_type argument
to gss_init_sec_context()).  The most common use of SPNEGO is for
HTTP-Negotiate, used in web browsers and other web clients.  Most such
clients are believed to not offer HTTP-Negotiate by default, instead
requiring a whitelist of sites for which it may be used to be
configured.  If the whitelist is configured to only allow
HTTP-Negotiate over TLS connections ("https://"), a successful
attacker must also spoof the web server's SSL certificate, due to the
way the WWW-Authenticate header is sent in a 401 (Unauthorized)
response message.  Unfortunately, many instructions for enabling
HTTP-Negotiate in common web browsers do not include a TLS
requirement.

    CVSSv2 Vector: AV:N/AC:H/Au:N/C:C/I:C/A:C/E:POC/RL:OF/RC:C

[kaduk@mit.edu: CVE summary and CVSSv2 vector]

ticket: 7969 (new)
target_version: 1.12.2
tags: pullup
CVE: CVE-2014-4343
CWE: CWE-415
Vulnerability description: Double free vulnerability in the init_ctx_reselect function in the SPNEGO initiator in lib/gssapi/spnego/spnego_mech.c in MIT Kerberos 5 (aka krb5) 1.10.x through 1.12.x before 1.12.2 allows remote attackers to cause a denial of service (memory corruption) or possibly execute arbitrary code via network traffic that appears to come from an intended acceptor, but specifies a security mechanism different from the one proposed by the initiator.

Project: krb5
File: spnego_mech.c

init_ctx_cont(OM_uint32 *minor_status, gss_ctx_id_t *ctx, gss_buffer_t buf,
	      gss_buffer_t *responseToken, gss_buffer_t *mechListMIC,
	      OM_uint32 *negState, send_token_flag *tokflag)
{
	OM_uint32 ret, tmpmin, acc_negState;
	unsigned char *ptr;
	spnego_gss_ctx_id_t sc;
	gss_OID supportedMech = GSS_C_NO_OID;

	sc = (spnego_gss_ctx_id_t)*ctx;
	*negState = REJECT;
	*tokflag = ERROR_TOKEN_SEND;

	ptr = buf->value;
	ret = get_negTokenResp(minor_status, ptr, buf->length,
			       &acc_negState, &supportedMech,
			       responseToken, mechListMIC);
	if (ret != GSS_S_COMPLETE)
		goto cleanup;
	if (acc_negState == REJECT) {
		*minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
		map_errcode(minor_status);
		*tokflag = NO_TOKEN_SEND;
		ret = GSS_S_FAILURE;
		goto cleanup;
	}
	/*
	 * nego_done is false for the first call to init_ctx_cont()
	 */
	if (!sc->nego_done) {
		ret = init_ctx_nego(minor_status, sc,
				    acc_negState,
				    supportedMech, responseToken,
				    mechListMIC,
				    negState, tokflag);
	} else if ((!sc->mech_complete && *responseToken == GSS_C_NO_BUFFER) ||
		   (sc->mech_complete && *responseToken != GSS_C_NO_BUFFER)) {
		/* Missing or spurious token from acceptor. */
		ret = GSS_S_DEFECTIVE_TOKEN;
	} else if (!sc->mech_complete ||
		   (sc->mic_reqd &&
		    (sc->ctx_flags & GSS_C_INTEG_FLAG))) {
		/* Not obviously done; we may decide we're done later in
		 * init_ctx_call_init or handle_mic. */
		*negState = ACCEPT_INCOMPLETE;
		*tokflag = CONT_TOKEN_SEND;
		ret = GSS_S_CONTINUE_NEEDED;
	} else {
		/* mech finished on last pass and no MIC required, so done. */
		*negState = ACCEPT_COMPLETE;
		*tokflag = NO_TOKEN_SEND;
		ret = GSS_S_COMPLETE;
	}
cleanup:
	if (supportedMech != GSS_C_NO_OID)
		generic_gss_release_oid(&tmpmin, &supportedMech);
	return ret;
}

Project: krb5
File: spnego_mech.c

mech_requires_mechlistMIC(spnego_gss_ctx_id_t sc)
{
	OM_uint32 major, minor;
	gss_ctx_id_t ctx = sc->ctx_handle;
	gss_OID oid = (gss_OID)&spnego_req_mechlistMIC_oid;
	gss_buffer_set_t bufs;
	int result;

	major = gss_inquire_sec_context_by_oid(&minor, ctx, oid, &bufs);
	if (major != GSS_S_COMPLETE)
		return 0;

	/* Report true if the mech returns a single buffer containing a single
	 * byte with value 1. */
	result = (bufs != NULL && bufs->count == 1 &&
		  bufs->elements[0].length == 1 &&
		  memcmp(bufs->elements[0].value, "\1", 1) == 0);
	(void) gss_release_buffer_set(&minor, &bufs);
	return result;
}