[31656] in CVS-changelog-for-Kerberos-V5
krb5 commit: Properly verify RPCSEC_GSS call arguments
daemon@ATHENA.MIT.EDU (ghudson@mit.edu)
Mon Nov 9 12:00:07 2026
From: ghudson@mit.edu
To: cvs-krb5@mit.edu
Message-Id: <20261109165952.A0001105814@krbdev.mit.edu>
Date: Mon, 9 Nov 2026 11:59:52 -0500 (EST)
MIME-Version: 1.0
Reply-To: krbdev@mit.edu
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Errors-To: cvs-krb5-bounces@mit.edu
https://github.com/krb5/krb5/commit/2cda39a6c0a2ee19b7cdd811e07d564506388fe3
commit 2cda39a6c0a2ee19b7cdd811e07d564506388fe3
Author: Alejandro Pacheco <alepacheco99@gmail.com>
Date: Thu Oct 15 22:27:14 2026 +0200
Properly verify RPCSEC_GSS call arguments
In gssrpc__svcauth_gss(), use a new helper svcauth_gss_check_call()
for RPCSEC_GSS_DATA and RPCSEC_GSS_DESTROY() messages, subsuming the
existing sequence number checking and svcauth_gss_validate() calls.
In the helper, set gd->sec.svc to the verified service value
(typically RPCSEC_GSS_SVC_PRIVACY) from the current message, to be
used when unwrapping call arguments and wrapping return arguments.
Check the sequence number before the verifier, and check the verifier
before updating the sequence window.
In svcauth_gss_accept_sec_context(), do not record the unverified
service value.
Add an RPCSEC_GSS mode to the gssrpc unit-test client. In this mode,
make a call at each service level after establishing the context at
each level.
[ghudson@mit.edu: edited commit message; made minor style edits]
CVE-2026-118380:
In MIT krb5 release 1.4 and later, an active network attacker can
alter the arguments sent by a legitimate client to RPC services
protected by RPCSEC_GSS, or cause return values to be sent in plain
text.
ticket: 9272 (new)
tags: pullup
target_version: 1.22-next
src/lib/rpc/svc_auth_gss.c | 82 +++++++++++++++++++++++++++------------
src/lib/rpc/unit-test/client.c | 88 ++++++++++++++++++++++++++++++++++++++++--
src/lib/rpc/unit-test/t_rpc.py | 3 ++
3 files changed, 145 insertions(+), 28 deletions(-)
diff --git a/src/lib/rpc/svc_auth_gss.c b/src/lib/rpc/svc_auth_gss.c
index aafa54f16..977c21985 100644
--- a/src/lib/rpc/svc_auth_gss.c
+++ b/src/lib/rpc/svc_auth_gss.c
@@ -231,7 +231,6 @@ svcauth_gss_accept_sec_context(struct svc_req *rqst,
/* Save client info. */
gd->sec.mech = mech;
gd->sec.qop = GSS_C_QOP_DEFAULT;
- gd->sec.svc = gc->gc_svc;
gd->seq = gc->gc_seq;
gd->win = gr->gr_win;
@@ -388,6 +387,53 @@ svcauth_gss_nextverf(struct svc_req *rqst, u_int num)
return (TRUE);
}
+/*
+ * Check an RPCSEC_GSS_DATA or RPCSEC_GSS_DESTROY call: verify its
+ * credential, then take its sequence number and service from it. Calls
+ * outside the sequence window or already seen are discarded before the
+ * verifier is checked, without changing any state.
+ */
+static enum auth_stat
+svcauth_gss_check_call(struct svc_req *rqst, struct svc_rpc_gss_data *gd,
+ struct rpc_gss_cred *gc, struct rpc_msg *msg,
+ bool_t *no_dispatch)
+{
+ int offset;
+
+ if (!gd->established)
+ return (RPCSEC_GSS_CREDPROBLEM);
+
+ if (gc->gc_seq > MAXSEQ)
+ return (RPCSEC_GSS_CTXPROBLEM);
+
+ offset = gd->seqlast - gc->gc_seq;
+ if (offset >= 0 && ((u_int)offset >= gd->win ||
+ (gd->seqmask & (1 << offset)))) {
+ *no_dispatch = 1;
+ return (RPCSEC_GSS_CTXPROBLEM);
+ }
+
+ if (!svcauth_gss_validate(rqst, gd, msg))
+ return (RPCSEC_GSS_CREDPROBLEM);
+
+ if (offset < 0) {
+ gd->seqlast = gc->gc_seq;
+ gd->seqmask <<= 0 - offset;
+ offset = 0;
+ }
+ gd->seqmask |= (1 << offset);
+ gd->seq = gc->gc_seq;
+
+ /*
+ * Protect the arguments and results of this call with the service
+ * named in its credential, which the verifier covers. The service
+ * sent during context establishment is not integrity-protected and
+ * is not used.
+ */
+ gd->sec.svc = gc->gc_svc;
+ return (AUTH_OK);
+}
+
enum auth_stat
gssrpc__svcauth_gss(struct svc_req *rqst, struct rpc_msg *msg,
bool_t *no_dispatch)
@@ -398,7 +444,8 @@ gssrpc__svcauth_gss(struct svc_req *rqst, struct rpc_msg *msg,
struct svc_rpc_gss_data *gd;
struct rpc_gss_cred *gc;
struct rpc_gss_init_res gr;
- int call_stat, offset;
+ enum auth_stat auth_stat;
+ int call_stat;
OM_uint32 min_stat;
log_debug("in svcauth_gss()");
@@ -460,25 +507,6 @@ gssrpc__svcauth_gss(struct svc_req *rqst, struct rpc_msg *msg,
gc->gc_svc != RPCSEC_GSS_SVC_PRIVACY)
ret_freegc (AUTH_BADCRED);
- /* Check sequence number. */
- if (gd->established) {
- if (gc->gc_seq > MAXSEQ)
- ret_freegc (RPCSEC_GSS_CTXPROBLEM);
-
- if ((offset = gd->seqlast - gc->gc_seq) < 0) {
- gd->seqlast = gc->gc_seq;
- offset = 0 - offset;
- gd->seqmask <<= offset;
- offset = 0;
- } else if ((u_int)offset >= gd->win ||
- (gd->seqmask & (1 << offset))) {
- *no_dispatch = 1;
- ret_freegc (RPCSEC_GSS_CTXPROBLEM);
- }
- gd->seq = gc->gc_seq;
- gd->seqmask |= (1 << offset);
- }
-
if (gd->established) {
rqst->rq_clntname = (char *)gd->client_name;
rqst->rq_svccred = (char *)gd->ctx;
@@ -522,8 +550,10 @@ gssrpc__svcauth_gss(struct svc_req *rqst, struct rpc_msg *msg,
break;
case RPCSEC_GSS_DATA:
- if (!svcauth_gss_validate(rqst, gd, msg))
- ret_freegc (RPCSEC_GSS_CREDPROBLEM);
+ auth_stat = svcauth_gss_check_call(rqst, gd, gc, msg,
+ no_dispatch);
+ if (auth_stat != AUTH_OK)
+ ret_freegc (auth_stat);
if (!svcauth_gss_nextverf(rqst, htonl(gc->gc_seq)))
ret_freegc (AUTH_FAILED);
@@ -533,8 +563,10 @@ gssrpc__svcauth_gss(struct svc_req *rqst, struct rpc_msg *msg,
if (rqst->rq_proc != NULLPROC)
ret_freegc (AUTH_FAILED); /* XXX ? */
- if (!svcauth_gss_validate(rqst, gd, msg))
- ret_freegc (RPCSEC_GSS_CREDPROBLEM);
+ auth_stat = svcauth_gss_check_call(rqst, gd, gc, msg,
+ no_dispatch);
+ if (auth_stat != AUTH_OK)
+ ret_freegc (auth_stat);
if (!svcauth_gss_nextverf(rqst, htonl(gc->gc_seq)))
ret_freegc (AUTH_FAILED);
diff --git a/src/lib/rpc/unit-test/client.c b/src/lib/rpc/unit-test/client.c
index 7965a4306..0c7af2aa2 100644
--- a/src/lib/rpc/unit-test/client.c
+++ b/src/lib/rpc/unit-test/client.c
@@ -18,6 +18,7 @@
#include <gssapi/gssapi_krb5.h>
#include <gssrpc/rpc.h>
#include <gssrpc/auth_gssapi.h>
+#include <gssrpc/auth_gss.h>
#include "rpc_test.h"
#define BIG_BUF 4096
@@ -44,11 +45,81 @@ __attribute__((noreturn))
#endif
static void usage(void)
{
- fprintf(stderr, "usage: %s {-t|-u} [-a] [-s num] [-m num] host service [count]\n",
+ fprintf(stderr, "usage: %s {-t|-u} [-a] [-g] [-s num] [-m num] host service [count]\n",
whoami);
exit(1);
}
+/*
+ * Establish an RPCSEC_GSS context using the service init_svc, then make
+ * an echo call with each service level. Return the number of failed
+ * calls.
+ */
+static int
+rpcsec_gss_svc_test(CLIENT *clnt, char *target, rpc_gss_svc_t init_svc)
+{
+ static const rpc_gss_svc_t svcs[] = {
+ RPCSEC_GSS_SVC_NONE, RPCSEC_GSS_SVC_INTEGRITY,
+ RPCSEC_GSS_SVC_PRIVACY
+ };
+ struct rpc_gss_sec sec = { 0 };
+ char buf[64], *echo_arg = buf, **echo_resp;
+ int failures = 0;
+ size_t i;
+
+ sec.mech = (gss_OID)gss_mech_krb5;
+ sec.qop = GSS_C_QOP_DEFAULT;
+ sec.svc = init_svc;
+ sec.cred = GSS_C_NO_CREDENTIAL;
+ sec.req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;
+ clnt->cl_auth = authgss_create_default(clnt, target, &sec);
+ if (clnt->cl_auth == NULL) {
+ clnt_pcreateerror(whoami);
+ exit(2);
+ }
+
+ for (i = 0; i < sizeof(svcs) / sizeof(*svcs); i++) {
+ if (!authgss_service(clnt->cl_auth, svcs[i])) {
+ fprintf(stderr, "authgss_service %d failed\n", (int)svcs[i]);
+ failures++;
+ continue;
+ }
+ snprintf(buf, sizeof(buf), "init svc %d, call svc %d",
+ (int)init_svc, (int)svcs[i]);
+ echo_resp = rpc_test_echo_1(&echo_arg, clnt);
+ if (echo_resp == NULL) {
+ fprintf(stderr, "RPCSEC_GSS echo (%s)%s", buf,
+ clnt_sperror(clnt, ""));
+ failures++;
+ continue;
+ }
+ if (strncmp(*echo_resp, "Echo: ", 6) != 0 ||
+ strcmp(echo_arg, *echo_resp + 6) != 0) {
+ fprintf(stderr, "RPCSEC_GSS echo (%s) response wrong: %s\n",
+ buf, *echo_resp);
+ failures++;
+ }
+ gssrpc_xdr_free((xdrproc_t)xdr_wrapstring, echo_resp);
+ }
+
+ AUTH_DESTROY(clnt->cl_auth);
+ clnt->cl_auth = NULL;
+ return failures;
+}
+
+/* Check that each RPCSEC_GSS call is protected with the service level
+ * it names, whatever level was used to establish the context. */
+static int
+rpcsec_gss_test(CLIENT *clnt, char *target)
+{
+ int failures = 0;
+
+ failures += rpcsec_gss_svc_test(clnt, target, RPCSEC_GSS_SVC_NONE);
+ failures += rpcsec_gss_svc_test(clnt, target, RPCSEC_GSS_SVC_INTEGRITY);
+ failures += rpcsec_gss_svc_test(clnt, target, RPCSEC_GSS_SVC_PRIVACY);
+ return failures;
+}
+
int
main(int argc, char **argv)
{
@@ -56,7 +127,7 @@ main(int argc, char **argv)
CLIENT *clnt;
AUTH *tmp_auth;
struct rpc_err e;
- int auth_once, sock, use_tcp;
+ int auth_once, sock, use_tcp, use_rpcsec_gss;
unsigned int count, i;
extern int optind;
extern char *optarg;
@@ -73,12 +144,16 @@ main(int argc, char **argv)
count = 1026;
auth_once = 0;
use_tcp = -1;
+ use_rpcsec_gss = 0;
- while ((c = getopt(argc, argv, "a:m:os:tu")) != -1) {
+ while ((c = getopt(argc, argv, "a:gm:os:tu")) != -1) {
switch (c) {
case 'a':
auth_debug_gssapi = atoi(optarg);
break;
+ case 'g':
+ use_rpcsec_gss = 1;
+ break;
case 'm':
misc_debug_gssapi = atoi(optarg);
break;
@@ -148,6 +223,13 @@ main(int argc, char **argv)
exit(1);
}
+ if (use_rpcsec_gss) {
+ if (rpcsec_gss_test(clnt, target) != 0)
+ exit(1);
+ CLNT_DESTROY(clnt);
+ exit(0);
+ }
+
clnt->cl_auth = auth_gssapi_create_default(clnt, target);
if (clnt->cl_auth == NULL) {
clnt_pcreateerror(whoami);
diff --git a/src/lib/rpc/unit-test/t_rpc.py b/src/lib/rpc/unit-test/t_rpc.py
index 5ab5d32df..e9a1ef960 100644
--- a/src/lib/rpc/unit-test/t_rpc.py
+++ b/src/lib/rpc/unit-test/t_rpc.py
@@ -18,6 +18,9 @@ for i in range(4):
fail('unexpected server message: ' + line)
output(line)
+mark('RPCSEC_GSS service levels')
+realm.run(['./client', '-t', '-g', hostname, portstr, 'host@' + hostname])
+
realm.addprinc('nokey/' + hostname)
realm.run(['./client', '-t', hostname, portstr, 'nokey@' + hostname, '1026'],
_______________________________________________
cvs-krb5 mailing list
cvs-krb5@mit.edu
https://mailman.mit.edu/mailman/listinfo/cvs-krb5