From: Michal Privoznik <[email protected]>

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 <[email protected]>
---

v2 of:

https://lists.libvirt.org/archives/list/[email protected]/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

Reply via email to