-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Xen Security Advisory CVE-2026-79604 / XSA-512
version 3
oxenstored: Unbounded accumulation of watches
UPDATES IN VERSION 3
====================
Public release.
ISSUE DESCRIPTION
=================
Oxenstored maintains two datastructures about watches; one global trie,
and one hashtable tracked per domain. When a xenbus reconnect is
requested, watches are not cleared out of the global trie.
IMPACT
======
A guest can cause unbounded memory usage in oxenstored. This can lead
to a system-wide DoS.
VULNERABLE SYSTEMS
==================
All version of Xen from 4.6 onwards are vulnerable.
Only systems using the Ocaml Xenstored implementation are vulnerable.
Systems using the C Xenstored implementation are not vulnerable.
MITIGATION
==========
There are no mitigations.
CREDITS
=======
Found by Anthropic using agents to study the security of open-source
projects, with Ada Logics validating and reporting.
RESOLUTION
==========
Applying the appropriate set of attached patches resolves this issue.
Note that patches for released versions are generally prepared to
apply to the stable branches, and may not apply cleanly to the most
recent release tarball. Downstreams are encouraged to update to the
tip of the stable branch before applying these patches.
For xen.git oxenstored:
xsa512-?.patch xen-unstable - Xen 4.18.x
xsa512-4.17-?.patch Xen 4.17.x
For xapi-project/oxenstored:
xsa512-oxenstored-?.patch oxenstored master
$ sha256sum xsa512*
c4f92a3e032f853a85cbd43391b6fd6a2c8f6f4caeee49bec9bc97a5279ce9a3 xsa512-1.patch
2cc57079eda8f0db34885e8be242d736f590ae8094c60a044b55b4889e10392f xsa512-2.patch
16bfa30473d4ba67dea69f1915357ecbe4aefc3b10bfd413f0bd7a7d2cfe2a80 xsa512-4.17-1.patch
7c23fb0e64429db074d7ec4cf7ba6ba319b69d5f1d059aa848c19b9ce148244c xsa512-4.17-2.patch
28c6267057ccf3508eee92dd82a9726e6b0d616fd7cc3e7dcee9cbfd3b0b80f7 xsa512-oxenstored-1.patch
00b9df35d9cc3ee393385a65725a2a681e31b9e00c4d1d76e0441e284f005707 xsa512-oxenstored-2.patch
$
DEPLOYMENT DURING EMBARGO
=========================
Deployment of the patches and/or mitigations described above (or
others which are substantially similar) is permitted during the
embargo, even on public-facing systems with untrusted guest users and
administrators.
But: Distribution of updated software is prohibited (except to other
members of the predisclosure list).
Predisclosure list members who wish to deploy significantly different
patches and/or mitigations, please contact the Xen Project Security
Team.
(Note: this during-embargo deployment notice is retained in
post-embargo publicly released Xen Project advisories, even though it
is then no longer applicable. This is to enable the community to have
oversight of the Xen Project Security Team's decisionmaking.)
For more information about permissible uses of embargoed information,
consult the Xen Project community's agreed Security Policy:
http://www.xenproject.org/security-policy.html
-----BEGIN PGP SIGNATURE-----
iQFABAEBCAAqFiEEI+MiLBRfRHX6gGCng/4UyVfoK9kFAmqf98gMHHBncEB4ZW4u
b3JnAAoJEIP+FMlX6CvZk9kIAMqGI+5EVMLUd++EmpLqiTyGPcMYcDexca9XIpfJ
Gx+DICPIRfRDCFtUG4jEeEDi8lmDvIKp5XXTNRkxyWF3uVKYDA5xCxekKOGmS40A
p46pGzVVzGeSKdm2FfxEDDyuhhET5QrZLN98rJBcoL0/aX5sI4UoDR/FfdFemoAV
BVrRG4WgQuBDa7+/t4jgZ8DysZbn/zmLoOtNIPVtaH5zvep0ehGQSnR8sAOEB+mV
tmiW3kQoR1hxlOF/p1iVKLoBdOtJktwJ+Tk6OCCssS5TNGlGcpTs0KPukkXKwrem
vYDYkHeUTf4rd3khUfCdW1S0Gc/3XSBtdL26VSj1xku5kMA=
=6TLi
-----END PGP SIGNATURE-----
From 5332a2c46db4f96e5d5a57100c55076fe5610beb Mon Sep 17 00:00:00 2001
From: Andrii Sultanov <andriy.sultanov@vates.tech>
Date: Thu, 20 Aug 2026 16:00:02 +0100
Subject: oxenstored: Factor out Process.do_reconnect()
The logic flow here is complicated. In preparation to fix a bug, factor out
reconnecting a xenbus connection, and fold History.reconnect into it's single
caller.
No functional change.
This is part of XSA-512 / CVE-2026-79604.
Signed-off-by: Andrii Sultanov <andriy.sultanov@vates.tech>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Andrii Sultanov <andriy.sultanov@vates.tech>
diff --git a/tools/ocaml/xenstored/history.ml b/tools/ocaml/xenstored/history.ml
index f03fb1832923..3474a62da230 100644
--- a/tools/ocaml/xenstored/history.ml
+++ b/tools/ocaml/xenstored/history.ml
@@ -39,10 +39,6 @@ let end_transaction txn con tid commit =
trim ~txn ();
success
-let reconnect con =
- trim ();
- Connection.do_reconnect con
-
let push (x: history_record) =
let dom = x.con.Connection.dom in
match dom with
diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
index 0c9c460a9915..bc68c54c9aba 100644
--- a/tools/ocaml/xenstored/process.ml
+++ b/tools/ocaml/xenstored/process.ml
@@ -350,6 +350,13 @@ let do_reset_watches con _t _domains cons _data =
Connections.del_watches cons con;
Connection.del_transactions con
+let do_reconnect cons con =
+ let domstr = Connection.get_domstr con in
+ info "%s requests a reconnect" domstr;
+ History.trim ();
+ Connection.do_reconnect con;
+ info "%s reconnection complete" domstr
+
(* only in >= xen3.3 *)
let do_set_target con _t _domains cons data =
if not (Connection.is_dom0 con)
@@ -735,9 +742,7 @@ let do_input store cons doms con =
if Connection.can_input con then Connection.do_input con
else None
with Xenbus.Xb.Reconnect ->
- info "%s requests a reconnect" (Connection.get_domstr con);
- History.reconnect con;
- info "%s reconnection complete" (Connection.get_domstr con);
+ do_reconnect cons con;
None
| Invalid_argument exp | Failure exp ->
error "caught exception %s" exp;
@@ -760,7 +765,7 @@ let do_input store cons doms con =
write_access_log ~ty ~tid ~con:(Connection.get_domstr con) ~data;
Connection.incr_ops con
-let do_output _store _cons _doms con =
+let do_output _store cons _doms con =
Connection.source_flush_watchevents con;
if Connection.has_output con then (
if Connection.has_new_output con then (
@@ -775,8 +780,6 @@ let do_output _store _cons _doms con =
try
ignore (Connection.do_output con)
with Xenbus.Xb.Reconnect ->
- info "%s requests a reconnect" (Connection.get_domstr con);
- History.reconnect con;
- info "%s reconnection complete" (Connection.get_domstr con)
+ do_reconnect cons con
)
From 8dc4ca50e62f2587a0213b078fc908fdfb54828b Mon Sep 17 00:00:00 2001
From: Andrii Sultanov <andriy.sultanov@vates.tech>
Date: Thu, 20 Aug 2026 15:00:02 +0000
Subject: oxenstored: Reset the watches trie on domain reconnect
oxenstored maintains two datastructures about watches; one global trie, and
one hashtable tracked per domain. Both need keeping in sync, and right now
the global trie is not emptied when a xenbus reconnect is requested.
This is basically the same bug as XSA-330, commit 491a077ed4c5
("tools/ocaml/xenstored: delete watch from trie too when resetting watches"),
just tickled via another path.
Arrange for both Process.do_reset_watches() and Process.do_reconnect() to
share a common codepath for the resetting of watches and transactions.
Notably, this means that the latter now calls Connections.del_watches() which
clears the global trie too.
Connections.del_watches() already calls Connection.del_watches() so remove the
re-clearing of the state from Connection.do_reconnect().
Move History.trim() into reset_watches_and_transactions() so it's on the
common path, and place it after removing the transactions rather than before.
This is part of XSA-512 / CVE-2026-79604.
Reported-by: David Korczynski <David@Adalogics.com>
Fixes: 674ad2be409d ("xenstore: extend the xenstore ring with a 'closing' signal")
Signed-off-by: Andrii Sultanov <andriy.sultanov@vates.tech>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Andrii Sultanov <andriy.sultanov@vates.tech>
diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
index d11011e16439..37eb2444b936 100644
--- a/tools/ocaml/xenstored/connection.ml
+++ b/tools/ocaml/xenstored/connection.ml
@@ -148,13 +148,11 @@ let mark_as_bad con =
let initial_next_tid = 1
let do_reconnect con =
+ (* transactions and watches handled by caller *)
Xenbus.Xb.reconnect con.xb;
(* dom is the same *)
- Hashtbl.clear con.transactions;
con.next_tid <- initial_next_tid;
- Hashtbl.clear con.watches;
(* anonid is the same *)
- con.nb_watches <- 0;
con.stat_nb_ops <- 0;
(* perm is the same *)
()
diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
index bc68c54c9aba..fc2558ac3d9b 100644
--- a/tools/ocaml/xenstored/process.ml
+++ b/tools/ocaml/xenstored/process.ml
@@ -345,15 +345,18 @@ let do_isintroduced con _t domains _cons data =
in
if domid = Define.domid_self || Domains.exist domains domid then "T\000" else "F\000"
-(* only in xen >= 4.2 *)
-let do_reset_watches con _t _domains cons _data =
+let reset_watches_and_transactions cons con =
Connections.del_watches cons con;
- Connection.del_transactions con
+ Connection.del_transactions con;
+ History.trim ()
+
+let do_reset_watches con _t _domains cons _data =
+ reset_watches_and_transactions cons con
let do_reconnect cons con =
let domstr = Connection.get_domstr con in
info "%s requests a reconnect" domstr;
- History.trim ();
+ reset_watches_and_transactions cons con;
Connection.do_reconnect con;
info "%s reconnection complete" domstr
From ca21f228fe8302f3fc724b2fff46b24906d9dcd7 Mon Sep 17 00:00:00 2001
From: Andrii Sultanov <andriy.sultanov@vates.tech>
Date: Thu, 20 Aug 2026 16:00:02 +0100
Subject: oxenstored: Factor out Process.do_reconnect()
The logic flow here is complicated. In preparation to fix a bug, factor out
reconnecting a xenbus connection, and fold History.reconnect into it's single
caller.
No functional change.
This is part of XSA-512 / CVE-2026-79604.
Signed-off-by: Andrii Sultanov <andriy.sultanov@vates.tech>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Andrii Sultanov <andriy.sultanov@vates.tech>
diff --git a/tools/ocaml/xenstored/history.ml b/tools/ocaml/xenstored/history.ml
index ba5c9cb571ff..029802bd1544 100644
--- a/tools/ocaml/xenstored/history.ml
+++ b/tools/ocaml/xenstored/history.ml
@@ -39,10 +39,6 @@ let end_transaction txn con tid commit =
trim ~txn ();
success
-let reconnect con =
- trim ();
- Connection.do_reconnect con
-
let push (x: history_record) =
let dom = x.con.Connection.dom in
match dom with
diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
index 02bd0f7d8098..33161da5eb8b 100644
--- a/tools/ocaml/xenstored/process.ml
+++ b/tools/ocaml/xenstored/process.ml
@@ -333,6 +333,13 @@ let do_reset_watches con _t _domains cons _data =
Connections.del_watches cons con;
Connection.del_transactions con
+let do_reconnect cons con =
+ let domstr = Connection.get_domstr con in
+ info "%s requests a reconnect" domstr;
+ History.trim ();
+ Connection.do_reconnect con;
+ info "%s reconnection complete" domstr
+
(* only in >= xen3.3 *)
let do_set_target con _t _domains cons data =
if not (Connection.is_dom0 con)
@@ -718,9 +725,7 @@ let do_input store cons doms con =
if Connection.can_input con then Connection.do_input con
else None
with Xenbus.Xb.Reconnect ->
- info "%s requests a reconnect" (Connection.get_domstr con);
- History.reconnect con;
- info "%s reconnection complete" (Connection.get_domstr con);
+ do_reconnect cons con;
None
| Invalid_argument exp | Failure exp ->
error "caught exception %s" exp;
@@ -743,7 +748,7 @@ let do_input store cons doms con =
write_access_log ~ty ~tid ~con:(Connection.get_domstr con) ~data;
Connection.incr_ops con
-let do_output _store _cons _doms con =
+let do_output _store cons _doms con =
Connection.source_flush_watchevents con;
if Connection.has_output con then (
if Connection.has_new_output con then (
@@ -758,8 +763,6 @@ let do_output _store _cons _doms con =
try
ignore (Connection.do_output con)
with Xenbus.Xb.Reconnect ->
- info "%s requests a reconnect" (Connection.get_domstr con);
- History.reconnect con;
- info "%s reconnection complete" (Connection.get_domstr con)
+ do_reconnect cons con;
)
From 66b310570996c69bf7954aabacdd6678630ef8ac Mon Sep 17 00:00:00 2001
From: Andrii Sultanov <andriy.sultanov@vates.tech>
Date: Thu, 20 Aug 2026 15:00:02 +0000
Subject: oxenstored: Reset the watches trie on domain reconnect
oxenstored maintains two datastructures about watches; one global trie, and
one hashtable tracked per domain. Both need keeping in sync, and right now
the global trie is not emptied when a xenbus reconnect is requested.
This is basically the same bug as XSA-330, commit 491a077ed4c5
("tools/ocaml/xenstored: delete watch from trie too when resetting watches"),
just tickled via another path.
Arrange for both Process.do_reset_watches() and Process.do_reconnect() to
share a common codepath for the resetting of watches and transactions.
Notably, this means that the latter now calls Connections.del_watches() which
clears the global trie too.
Connections.del_watches() already calls Connection.del_watches() so remove the
re-clearing of the state from Connection.do_reconnect().
Move History.trim() into reset_watches_and_transactions() so it's on the
common path, and place it after removing the transactions rather than before.
This is part of XSA-512 / CVE-2026-79604.
Reported-by: David Korczynski <David@Adalogics.com>
Fixes: 674ad2be409d ("xenstore: extend the xenstore ring with a 'closing' signal")
Signed-off-by: Andrii Sultanov <andriy.sultanov@vates.tech>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Andrii Sultanov <andriy.sultanov@vates.tech>
diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
index 54f7f765167b..a91caabec674 100644
--- a/tools/ocaml/xenstored/connection.ml
+++ b/tools/ocaml/xenstored/connection.ml
@@ -148,13 +148,11 @@ let mark_as_bad con =
let initial_next_tid = 1
let do_reconnect con =
+ (* transactions and watches handled by caller *)
Xenbus.Xb.reconnect con.xb;
(* dom is the same *)
- Hashtbl.clear con.transactions;
con.next_tid <- initial_next_tid;
- Hashtbl.clear con.watches;
(* anonid is the same *)
- con.nb_watches <- 0;
con.stat_nb_ops <- 0;
(* perm is the same *)
()
diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
index 33161da5eb8b..b2ee4d0adf1b 100644
--- a/tools/ocaml/xenstored/process.ml
+++ b/tools/ocaml/xenstored/process.ml
@@ -328,15 +328,18 @@ let do_isintroduced con _t domains _cons data =
in
if domid = Define.domid_self || Domains.exist domains domid then "T\000" else "F\000"
-(* only in xen >= 4.2 *)
-let do_reset_watches con _t _domains cons _data =
+let reset_watches_and_transactions cons con =
Connections.del_watches cons con;
- Connection.del_transactions con
+ Connection.del_transactions con;
+ History.trim ()
+
+let do_reset_watches con _t _domains cons _data =
+ reset_watches_and_transactions cons con
let do_reconnect cons con =
let domstr = Connection.get_domstr con in
info "%s requests a reconnect" domstr;
- History.trim ();
+ reset_watches_and_transactions cons con;
Connection.do_reconnect con;
info "%s reconnection complete" domstr
From 549312692de1d22e70be75b9339ef1e575baa964 Mon Sep 17 00:00:00 2001
From: Andrii Sultanov <andriy.sultanov@vates.tech>
Date: Thu, 20 Aug 2026 16:00:02 +0100
Subject: oxenstored: Factor out Process.do_reconnect()
The logic flow here is complicated. In preparation to fix a bug, factor out
reconnecting a xenbus connection, and fold History.reconnect into it's single
caller.
No functional change.
This is part of XSA-512 / CVE-2026-79604.
Signed-off-by: Andrii Sultanov <andriy.sultanov@vates.tech>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Andrii Sultanov <andriy.sultanov@vates.tech>
diff --git a/oxenstored/history.ml b/oxenstored/history.ml
index be1994f18020..490d7a0ee66e 100644
--- a/oxenstored/history.ml
+++ b/oxenstored/history.ml
@@ -41,10 +41,6 @@ let end_transaction txn con tid commit =
let success = Connection.end_transaction con tid commit in
trim ~txn () ; success
-let reconnect con =
- trim () ;
- Connection.do_reconnect con
-
let push (x : history_record) =
let dom = x.con.Connection.dom in
match dom with
diff --git a/oxenstored/process.ml b/oxenstored/process.ml
index 477b4e41b3be..523ad4faafd8 100644
--- a/oxenstored/process.ml
+++ b/oxenstored/process.ml
@@ -477,6 +477,13 @@ let do_reset_watches con _t _domains cons _data =
Connections.del_watches cons con ;
Connection.del_transactions con
+let do_reconnect cons con =
+ let domstr = Connection.get_domstr con in
+ info "%s requests a reconnect" domstr ;
+ History.trim () ;
+ Connection.do_reconnect con ;
+ info "%s reconnection complete" domstr
+
(* only in >= xen3.3 *)
let do_set_target con _t _domains cons data =
if not (Connection.is_dom0 con) then
@@ -976,10 +983,7 @@ let do_input store cons doms con =
None
with
| Xenbus.Xb.Reconnect ->
- info "%s requests a reconnect" (Connection.get_domstr con) ;
- History.reconnect con ;
- info "%s reconnection complete" (Connection.get_domstr con) ;
- None
+ do_reconnect cons con ; None
| Invalid_argument exp | Failure exp ->
error "caught exception %s" exp ;
error "got a bad client %s" (sprintf "%-8s" (Connection.get_domstr con)) ;
@@ -1002,7 +1006,7 @@ let do_input store cons doms con =
write_access_log ~ty ~tid ~con:(Connection.get_domstr con) ~data ;
Connection.incr_ops con
-let do_output _store _cons _doms con =
+let do_output _store cons _doms con =
Connection.source_flush_watchevents con ;
if Connection.has_output con then (
( if Connection.has_new_output con then
@@ -1015,8 +1019,5 @@ let do_output _store _cons _doms con =
write_answer_log ~ty ~tid ~con:(Connection.get_domstr con) ~data
) ;
try ignore (Connection.do_output con)
- with Xenbus.Xb.Reconnect ->
- info "%s requests a reconnect" (Connection.get_domstr con) ;
- History.reconnect con ;
- info "%s reconnection complete" (Connection.get_domstr con)
+ with Xenbus.Xb.Reconnect -> do_reconnect cons con
)
From da47b5969576d60e8864b5ecc4fd0db67e7e4601 Mon Sep 17 00:00:00 2001
From: Andrii Sultanov <andriy.sultanov@vates.tech>
Date: Thu, 20 Aug 2026 15:00:02 +0000
Subject: oxenstored: Reset the watches trie on domain reconnect
oxenstored maintains two datastructures about watches; one global trie, and
one hashtable tracked per domain. Both need keeping in sync, and right now
the global trie is not emptied when a xenbus reconnect is requested.
This is basically the same bug as XSA-330, commit 910388bb4f37
("tools/ocaml/xenstored: delete watch from trie too when resetting watches"),
just tickled via another path.
Arrange for both Process.do_reset_watches() and Process.do_reconnect() to
share a common codepath for the resetting of watches and transactions.
Notably, this means that the latter now calls Connections.del_watches() which
clears the global trie too.
Connections.del_watches() already calls Connection.del_watches() so remove the
re-clearing of the state from Connection.do_reconnect().
Move History.trim() into reset_watches_and_transactions() so it's on the
common path, and place it after removing the transactions rather than before.
This is part of XSA-512 / CVE-2026-79604.
Reported-by: David Korczynski <David@Adalogics.com>
Fixes: 674ad2be409d ("xenstore: extend the xenstore ring with a 'closing' signal")
Signed-off-by: Andrii Sultanov <andriy.sultanov@vates.tech>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Andrii Sultanov <andriy.sultanov@vates.tech>
diff --git a/oxenstored/connection.ml b/oxenstored/connection.ml
index 29d1911f3179..58b90a8faa5c 100644
--- a/oxenstored/connection.ml
+++ b/oxenstored/connection.ml
@@ -193,13 +193,11 @@ let mark_as_bad con =
let initial_next_tid = 1
let do_reconnect con =
+ (* transactions and watches handled by caller *)
Xenbus.Xb.reconnect con.xb ;
(* dom is the same *)
- Hashtbl.clear con.transactions ;
con.next_tid <- initial_next_tid ;
- Hashtbl.clear con.watches ;
(* anonid is the same *)
- con.nb_watches <- 0 ;
con.stat_nb_ops <- 0 ;
(* perm is the same *)
()
diff --git a/oxenstored/process.ml b/oxenstored/process.ml
index 523ad4faafd8..038cdb72be0b 100644
--- a/oxenstored/process.ml
+++ b/oxenstored/process.ml
@@ -472,15 +472,18 @@ let do_isintroduced con _t domains _cons data =
else
"F\000"
-(* only in xen >= 4.2 *)
-let do_reset_watches con _t _domains cons _data =
+let reset_watches_and_transactions cons con =
Connections.del_watches cons con ;
- Connection.del_transactions con
+ Connection.del_transactions con ;
+ History.trim ()
+
+let do_reset_watches con _t _domains cons _data =
+ reset_watches_and_transactions cons con
let do_reconnect cons con =
let domstr = Connection.get_domstr con in
info "%s requests a reconnect" domstr ;
- History.trim () ;
+ reset_watches_and_transactions cons con ;
Connection.do_reconnect con ;
info "%s reconnection complete" domstr
© 2016 - 2026 Red Hat, Inc.