The parser now rejects line breaks in typed DNS TXT and SRV fields,
but the dnsmasq configuration emitter is the actual trust boundary:
any future parser gap, or a value reaching the emitter by another
path, would again let a typed DNS field inject an arbitrary dnsmasq
directive.
Add a defensive check in networkDnsmasqConfContents() that rejects
LF and CR in every typed DNS string immediately before it is written
to the line-based configuration file. This sits behind the parser
checks and keeps the emitter correct on its own.
The raw <dnsmasq:options> namespace is intentionally left untouched:
it is the documented escape hatch for arbitrary dnsmasq options, and
sanitizing it would be a separate, deliberate behavior change.
CVE-2026-61477
Fixes: 8b32c80df089 ("network: put dnsmasq parameters in conf-file instead of
command line")
Signed-off-by: Michael Bommarito <[email protected]>
---
src/network/bridge_driver.c | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
index 4dc3e5424b..6ebdc27e76 100644
--- a/src/network/bridge_driver.c
+++ b/src/network/bridge_driver.c
@@ -64,6 +64,7 @@
#include "virhook.h"
#include "virjson.h"
#include "virnetworkportdef.h"
+#include "virstring.h"
#include "virutil.h"
#include "virsystemd.h"
#include "netdev_bandwidth_conf.h"
@@ -119,6 +120,22 @@ networkDnsmasqDefNamespaceFree(void *nsdata)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(networkDnsmasqXmlNsDef,
networkDnsmasqDefNamespaceFree);
+static int
+networkDnsmasqConfCheckLineBreaks(const char *record,
+ const char *field,
+ const char *value)
+{
+ if (virStringHasChars(value, "\r\n")) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("DNS %1$s record %2$s must not contain line breaks"),
+ record, field);
+ return -1;
+ }
+
+ return 0;
+}
+
+
static int
networkDnsmasqDefNamespaceParseOptions(networkDnsmasqXmlNsDef *nsdef,
xmlXPathContextPtr ctxt)
@@ -1300,6 +1317,10 @@ networkDnsmasqConfContents(virNetworkObj *obj,
if (wantDNS) {
for (i = 0; i < dns->ntxts; i++) {
+ if (networkDnsmasqConfCheckLineBreaks("TXT", "name",
dns->txts[i].name) < 0 ||
+ networkDnsmasqConfCheckLineBreaks("TXT", "value",
dns->txts[i].value) < 0)
+ return -1;
+
virBufferAsprintf(&configbuf, "txt-record=%s,%s\n",
dns->txts[i].name,
dns->txts[i].value);
@@ -1321,6 +1342,13 @@ networkDnsmasqConfContents(virNetworkObj *obj,
def->name);
return -1;
}
+
+ if (networkDnsmasqConfCheckLineBreaks("SRV", "service",
dns->srvs[i].service) < 0 ||
+ networkDnsmasqConfCheckLineBreaks("SRV", "protocol",
dns->srvs[i].protocol) < 0 ||
+ networkDnsmasqConfCheckLineBreaks("SRV", "domain",
dns->srvs[i].domain) < 0 ||
+ networkDnsmasqConfCheckLineBreaks("SRV", "target",
dns->srvs[i].target) < 0)
+ return -1;
+
/* RFC2782 requires that service and protocol be preceded by
* an underscore.
*/
--
2.53.0