src/remote/remote_daemon_dispatch.c | 2 +- src/rpc/virnetsaslcontext.c | 31 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-)
From: Michal Privoznik <mprivozn@redhat.com>
The usual flow of SASL is REMOTE_PROC_AUTH_SASL_INIT followed by
REMOTE_PROC_AUTH_SASL_START which can either complete
authentication or require further steps which are then done by
calling REMOTE_PROC_AUTH_SASL_STEP (multiple times even).
But if a malicious client skips the SASL_START step and calls
SASL_INIT followed by SASL_STEP then the 'sasl->conn' structure
inside of virNetSASLSessionServerStep() is not fully allocated
(most notably the list of auth mechanisms 'mech' is NULL). But
then sasl_server_step() dereferences that pointer leading to a
crash.
Fix this by disallowing SASL_STEP until SASL_START completed
successfully.
Fixes: CVE-2026-119151
Resolves: https://gitlab.com/libvirt/libvirt/-/work_items/940
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
---
v2 of:
https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/3R2JC6KASBO45SAC7HYFD6QGLZWWYLWJ/
diff to v1:
- Implement a state machine to allow just INIT -> START [-> STEP [->
STEP ...]] and nothing else,
- Drop checks additional from remote_daemon_dispatch.c (they were only
there to report a nicer error message anyways).
src/remote/remote_daemon_dispatch.c | 2 +-
src/rpc/virnetsaslcontext.c | 31 +++++++++++++++++++++++++++++
2 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c
index e548438250..2f1861f5bb 100644
--- a/src/remote/remote_daemon_dispatch.c
+++ b/src/remote/remote_daemon_dispatch.c
@@ -4034,7 +4034,7 @@ remoteDispatchAuthSaslStep(virNetServer *server,
VIR_DEBUG("Step SASL auth %d", virNetServerClientGetFD(client));
if (virNetServerClientGetAuth(client) != VIR_NET_SERVER_SERVICE_AUTH_SASL ||
priv->sasl == NULL) {
- VIR_ERROR(_("client tried invalid SASL start request"));
+ VIR_ERROR(_("client tried invalid SASL step request"));
goto authfail;
}
diff --git a/src/rpc/virnetsaslcontext.c b/src/rpc/virnetsaslcontext.c
index 8692703117..325ebd915e 100644
--- a/src/rpc/virnetsaslcontext.c
+++ b/src/rpc/virnetsaslcontext.c
@@ -37,12 +37,20 @@ struct _virNetSASLContext {
unsigned int tcpMinSSF;
};
+typedef enum virNetSASLSessionState virNetSASLSessionState;
+enum virNetSASLSessionState {
+ VIR_NET_SASL_SESSION_STATE_INIT,
+ VIR_NET_SASL_SESSION_STATE_START,
+ VIR_NET_SASL_SESSION_STATE_STEP
+};
+
struct _virNetSASLSession {
virObjectLockable parent;
sasl_conn_t *conn;
size_t maxbufsize;
sasl_callback_t *callbacks;
+ virNetSASLSessionState state;
};
@@ -248,6 +256,8 @@ virNetSASLSession *virNetSASLSessionNewServer(virNetSASLContext *ctxt G_GNUC_UNU
goto cleanup;
}
+ sasl->state = VIR_NET_SASL_SESSION_STATE_INIT;
+
return sasl;
cleanup:
@@ -529,6 +539,13 @@ int virNetSASLSessionServerStart(virNetSASLSession *sasl,
int ret = -1;
virObjectLock(sasl);
+
+ if (sasl->state != VIR_NET_SASL_SESSION_STATE_INIT) {
+ virReportError(VIR_ERR_AUTH_FAILED, "%s",
+ _("unexpected state of SASL session"));
+ goto cleanup;
+ }
+
err = sasl_server_start(sasl->conn,
mechname,
clientin,
@@ -542,12 +559,15 @@ int virNetSASLSessionServerStart(virNetSASLSession *sasl,
case SASL_OK:
if (virNetSASLSessionUpdateBufSize(sasl) < 0)
goto cleanup;
+ sasl->state = VIR_NET_SASL_SESSION_STATE_START;
ret = VIR_NET_SASL_COMPLETE;
break;
case SASL_CONTINUE:
+ sasl->state = VIR_NET_SASL_SESSION_STATE_START;
ret = VIR_NET_SASL_CONTINUE;
break;
case SASL_INTERACT:
+ sasl->state = VIR_NET_SASL_SESSION_STATE_START;
ret = VIR_NET_SASL_INTERACT;
break;
default:
@@ -575,6 +595,14 @@ int virNetSASLSessionServerStep(virNetSASLSession *sasl,
int ret = -1;
virObjectLock(sasl);
+
+ if (sasl->state != VIR_NET_SASL_SESSION_STATE_START &&
+ sasl->state != VIR_NET_SASL_SESSION_STATE_STEP) {
+ virReportError(VIR_ERR_AUTH_FAILED, "%s",
+ _("unexpected state of SASL session"));
+ goto cleanup;
+ }
+
err = sasl_server_step(sasl->conn,
clientin,
inlen,
@@ -587,12 +615,15 @@ int virNetSASLSessionServerStep(virNetSASLSession *sasl,
case SASL_OK:
if (virNetSASLSessionUpdateBufSize(sasl) < 0)
goto cleanup;
+ sasl->state = VIR_NET_SASL_SESSION_STATE_STEP;
ret = VIR_NET_SASL_COMPLETE;
break;
case SASL_CONTINUE:
+ sasl->state = VIR_NET_SASL_SESSION_STATE_STEP;
ret = VIR_NET_SASL_CONTINUE;
break;
case SASL_INTERACT:
+ sasl->state = VIR_NET_SASL_SESSION_STATE_STEP;
ret = VIR_NET_SASL_INTERACT;
break;
default:
--
2.56.0
© 2016 - 2026 Red Hat, Inc.