mirror of
https://github.com/libvirt/libvirt.git
synced 2026-09-03 20:53:04 -05:00
Add negative tests that feed XML numeric character references for LF ( ) and CR ( ) into the DNS TXT value and SRV domain/target attributes, covering both the network XML parse path and the update API. Literal newlines are insufficient because XML parsers normalize raw attribute whitespace to spaces; the numeric references are what survive to the configuration emitter. CVE-2026-61477 Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
386 lines
12 KiB
C
386 lines
12 KiB
C
#include <config.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "internal.h"
|
|
#include "testutils.h"
|
|
#include "network_conf.h"
|
|
#include "bridge_driver.h"
|
|
#define LIBVIRT_BRIDGE_DRIVER_PRIV_H_ALLOW
|
|
#include "bridge_driver_priv.h"
|
|
#define LIBVIRT_VIRCOMMANDPRIV_H_ALLOW
|
|
#include "vircommandpriv.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_NONE
|
|
|
|
typedef enum {
|
|
TEST_COMPARE_NET_XML2XML_RESULT_SUCCESS,
|
|
TEST_COMPARE_NET_XML2XML_RESULT_FAIL_PARSE,
|
|
TEST_COMPARE_NET_XML2XML_RESULT_FAIL_VALIDATE,
|
|
TEST_COMPARE_NET_XML2XML_RESULT_FAIL_FORMAT,
|
|
TEST_COMPARE_NET_XML2XML_RESULT_FAIL_COMPARE,
|
|
} testCompareNetXML2XMLResult;
|
|
|
|
struct _testInfo {
|
|
const char *name;
|
|
unsigned int flags;
|
|
testCompareNetXML2XMLResult expectResult;
|
|
virNetworkXMLOption *xmlopt; /* borrowed, immutable */
|
|
dnsmasqCaps *caps;
|
|
virNetworkDef *def;
|
|
char *inxml;
|
|
char *outxml;
|
|
char *outconf;
|
|
char *outhostsfile;
|
|
GHashTable *existingTestCases;
|
|
};
|
|
|
|
typedef struct _testInfo testInfo;
|
|
void testInfoFree(testInfo *info);
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(testInfo, testInfoFree);
|
|
|
|
void testInfoFree(testInfo *info)
|
|
{
|
|
if (!info)
|
|
return;
|
|
|
|
virNetworkDefFree(info->def);
|
|
VIR_FREE(info->inxml);
|
|
VIR_FREE(info->outxml);
|
|
VIR_FREE(info->outconf);
|
|
VIR_FREE(info->outhostsfile);
|
|
VIR_FREE(info);
|
|
}
|
|
|
|
static int
|
|
testCompareXMLToXMLFiles(const void *data)
|
|
{
|
|
testInfo *info = (void *) data;
|
|
g_autofree char *actual = NULL;
|
|
int ret;
|
|
testCompareNetXML2XMLResult result = TEST_COMPARE_NET_XML2XML_RESULT_SUCCESS;
|
|
g_autoptr(virNetworkDef) def = NULL;
|
|
|
|
virTestCaseMarkUsed(info->existingTestCases, info->inxml);
|
|
if (!(def = virNetworkDefParse(NULL, info->inxml, info->xmlopt, false))) {
|
|
result = TEST_COMPARE_NET_XML2XML_RESULT_FAIL_PARSE;
|
|
goto cleanup;
|
|
}
|
|
if (info->expectResult == TEST_COMPARE_NET_XML2XML_RESULT_FAIL_PARSE)
|
|
goto cleanup;
|
|
|
|
if (networkValidateTests(def) < 0) {
|
|
result = TEST_COMPARE_NET_XML2XML_RESULT_FAIL_VALIDATE;
|
|
goto cleanup;
|
|
}
|
|
if (info->expectResult == TEST_COMPARE_NET_XML2XML_RESULT_FAIL_VALIDATE)
|
|
goto cleanup;
|
|
|
|
if (!(actual = virNetworkDefFormat(def, info->xmlopt, info->flags))) {
|
|
result = TEST_COMPARE_NET_XML2XML_RESULT_FAIL_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
if (info->expectResult == TEST_COMPARE_NET_XML2XML_RESULT_FAIL_FORMAT)
|
|
goto cleanup;
|
|
|
|
virTestCaseMarkUsed(info->existingTestCases, info->outxml);
|
|
if (virTestCompareToFile(actual, info->outxml) < 0) {
|
|
result = TEST_COMPARE_NET_XML2XML_RESULT_FAIL_COMPARE;
|
|
goto cleanup;
|
|
}
|
|
if (info->expectResult == TEST_COMPARE_NET_XML2XML_RESULT_FAIL_COMPARE)
|
|
goto cleanup;
|
|
|
|
cleanup:
|
|
if (result == info->expectResult) {
|
|
ret = 0;
|
|
if (info->expectResult != TEST_COMPARE_NET_XML2XML_RESULT_SUCCESS) {
|
|
VIR_TEST_DEBUG("Got expected failure code=%d msg=%s",
|
|
result, virGetLastErrorMessage());
|
|
} else {
|
|
info->def = g_steal_pointer(&def);
|
|
}
|
|
} else {
|
|
ret = -1;
|
|
VIR_TEST_DEBUG("Expected result code=%d but received code=%d",
|
|
info->expectResult, result);
|
|
}
|
|
virResetLastError();
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
testCompareXMLToConfFiles(const void *data)
|
|
{
|
|
testInfo *info = (void *) data;
|
|
char *confactual = NULL;
|
|
g_autofree char *hostsfileactual = NULL;
|
|
int ret = -1;
|
|
virNetworkDef *def = NULL;
|
|
virNetworkObj *obj = NULL;
|
|
g_autofree char *pidfile = NULL;
|
|
g_autoptr(dnsmasqContext) dctx = NULL;
|
|
bool compareFailed = false;
|
|
|
|
if (!(obj = virNetworkObjNew()))
|
|
goto cleanup;
|
|
|
|
if (!(def = g_steal_pointer(&info->def))) {
|
|
/* Previous test wasn't executed. */
|
|
virTestCaseMarkUsed(info->existingTestCases, info->inxml);
|
|
if (!(def = virNetworkDefParse(NULL, info->inxml, info->xmlopt, false)))
|
|
goto cleanup;
|
|
|
|
if (networkValidateTests(def) < 0) {
|
|
virNetworkDefFree(def);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
virNetworkObjSetDef(obj, def);
|
|
|
|
if (!networkNeedsDnsmasq(def)) {
|
|
ret = EXIT_AM_SKIP;
|
|
goto cleanup;
|
|
}
|
|
|
|
dctx = dnsmasqContextNew(def->name, "/var/lib/libvirt/dnsmasq");
|
|
|
|
if (dctx == NULL)
|
|
goto cleanup;
|
|
|
|
if (networkDnsmasqConfContents(obj, pidfile, &confactual,
|
|
&hostsfileactual, dctx, info->caps) < 0)
|
|
goto cleanup;
|
|
|
|
/* Any changes to this function ^^ should be reflected here too. */
|
|
#ifndef __linux__
|
|
{
|
|
char * tmp;
|
|
|
|
if (!(tmp = virStringReplace(confactual,
|
|
"except-interface=lo0\n",
|
|
"except-interface=lo\n")))
|
|
goto cleanup;
|
|
VIR_FREE(confactual);
|
|
confactual = g_steal_pointer(&tmp);
|
|
}
|
|
#endif
|
|
|
|
virTestCaseMarkUsed(info->existingTestCases, info->outconf);
|
|
if (virTestCompareToFile(confactual, info->outconf) < 0)
|
|
compareFailed = true;
|
|
|
|
if (hostsfileactual) {
|
|
virTestCaseMarkUsed(info->existingTestCases, info->outhostsfile);
|
|
if (virTestCompareToFile(hostsfileactual, info->outhostsfile) < 0) {
|
|
compareFailed = true;
|
|
}
|
|
} else {
|
|
if (virFileExists(info->outhostsfile)) {
|
|
VIR_TEST_DEBUG("%s: hostsfile exists but the configuration did not specify any host",
|
|
info->outhostsfile);
|
|
compareFailed = true;
|
|
}
|
|
}
|
|
|
|
if (compareFailed)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
VIR_FREE(confactual);
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
buildCapsCallback(const char *const*args,
|
|
const char *const*env G_GNUC_UNUSED,
|
|
const char *input G_GNUC_UNUSED,
|
|
char **output,
|
|
char **error G_GNUC_UNUSED,
|
|
int *status,
|
|
void *opaque G_GNUC_UNUSED)
|
|
{
|
|
if (STREQ(args[0], "/usr/sbin/dnsmasq") && STREQ(args[1], "--version")) {
|
|
*output = g_strdup("Dnsmasq version 2.67\n");
|
|
*status = EXIT_SUCCESS;
|
|
} else {
|
|
*status = EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
|
|
static dnsmasqCaps *
|
|
buildCaps(void)
|
|
{
|
|
g_autoptr(dnsmasqCaps) caps = NULL;
|
|
g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew();
|
|
|
|
virCommandSetDryRun(dryRunToken, NULL, true, true, buildCapsCallback, NULL);
|
|
|
|
caps = dnsmasqCapsNewFromBinary();
|
|
|
|
return g_steal_pointer(&caps);
|
|
}
|
|
|
|
|
|
static void
|
|
testRun(const char *name,
|
|
int *ret,
|
|
virNetworkXMLOption *xmlopt,
|
|
dnsmasqCaps *caps,
|
|
testCompareNetXML2XMLResult expectResult,
|
|
GHashTable *existingTestCases,
|
|
unsigned int flags)
|
|
{
|
|
g_autofree char *name_xml2xml = g_strdup_printf("Network XML-2-XML %s", name);
|
|
g_autofree char *name_xml2conf = g_strdup_printf("Network XML-2-Conf %s", name);
|
|
g_autoptr(testInfo) info = g_new0(testInfo, 1);
|
|
|
|
info->name = name;
|
|
info->flags = flags;
|
|
info->expectResult = expectResult;
|
|
info->xmlopt = xmlopt;
|
|
info->caps = caps;
|
|
info->inxml = g_strdup_printf("%s/networkxmlconfdata/%s.xml", abs_srcdir, name);
|
|
info->outxml = g_strdup_printf("%s/networkxmlconfdata/%s.expect.xml", abs_srcdir, name);
|
|
info->outconf = g_strdup_printf("%s/networkxmlconfdata/%s.conf", abs_srcdir, name);
|
|
info->outhostsfile = g_strdup_printf("%s/networkxmlconfdata/%s.hostsfile", abs_srcdir, name);
|
|
info->existingTestCases = existingTestCases;
|
|
|
|
virTestRunLog(ret, name_xml2xml, testCompareXMLToXMLFiles, info);
|
|
|
|
if (expectResult == TEST_COMPARE_NET_XML2XML_RESULT_SUCCESS)
|
|
virTestRunLog(ret, name_xml2conf, testCompareXMLToConfFiles, info);
|
|
}
|
|
|
|
|
|
static bool
|
|
testCaseEnumerate(struct dirent *ent)
|
|
{
|
|
#ifndef __linux__
|
|
/* This test case is ran only on Linux. See comment in mymain(). */
|
|
if (STREQ(ent->d_name, "hostdev.expect.xml"))
|
|
return false;
|
|
#endif
|
|
return virStringHasSuffix(ent->d_name, ".xml") ||
|
|
virStringHasSuffix(ent->d_name, ".conf") ||
|
|
virStringHasSuffix(ent->d_name, ".hostsfile");
|
|
}
|
|
|
|
|
|
static int
|
|
mymain(void)
|
|
{
|
|
g_autoptr(virNetworkXMLOption) xmlopt = NULL;
|
|
g_autoptr(dnsmasqCaps) caps = NULL;
|
|
g_autoptr(GHashTable) existingTestCases = NULL;
|
|
int ret = 0;
|
|
|
|
if (virTestEnumerateTestCases(abs_srcdir "/networkxmlconfdata",
|
|
testCaseEnumerate,
|
|
&existingTestCases) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!(xmlopt = networkDnsmasqCreateXMLConf()))
|
|
return -1;
|
|
|
|
if (!(caps = buildCaps()))
|
|
return -1;
|
|
|
|
#define DO_TEST_FULL(name, flags, expectResult) \
|
|
testRun(name, &ret, xmlopt, caps, expectResult, existingTestCases, flags)
|
|
#define DO_TEST(name) \
|
|
DO_TEST_FULL(name, 0, TEST_COMPARE_NET_XML2XML_RESULT_SUCCESS)
|
|
#define DO_TEST_FLAGS(name, flags) \
|
|
DO_TEST_FULL(name, flags, TEST_COMPARE_NET_XML2XML_RESULT_SUCCESS)
|
|
#define DO_TEST_PARSE_ERROR(name) \
|
|
DO_TEST_FULL(name, 0, TEST_COMPARE_NET_XML2XML_RESULT_FAIL_PARSE)
|
|
#define DO_TEST_VALIDATE_ERROR(name) \
|
|
DO_TEST_FULL(name, 0, TEST_COMPARE_NET_XML2XML_RESULT_FAIL_VALIDATE)
|
|
|
|
DO_TEST("dhcp6-network");
|
|
DO_TEST("dhcp6-nat-network");
|
|
DO_TEST("dhcp6host-routed-network");
|
|
DO_TEST("empty-allow-ipv6");
|
|
DO_TEST("isolated-network");
|
|
DO_TEST("routed-network");
|
|
DO_TEST("routed-network-no-dns");
|
|
DO_TEST_PARSE_ERROR("routed-network-no-dns-extra-elements");
|
|
DO_TEST("open-network");
|
|
DO_TEST_PARSE_ERROR("open-network-with-forward-dev");
|
|
DO_TEST("nat-network");
|
|
DO_TEST("netboot-network");
|
|
DO_TEST("netboot-proxy-network");
|
|
DO_TEST("netboot-tftp");
|
|
DO_TEST("nat-network-dns-txt-record");
|
|
DO_TEST_PARSE_ERROR("nat-network-dns-txt-record-newline");
|
|
DO_TEST("nat-network-dns-srv-record");
|
|
DO_TEST_PARSE_ERROR("nat-network-dns-srv-record-domain-newline");
|
|
DO_TEST_PARSE_ERROR("nat-network-dns-srv-record-target-newline");
|
|
DO_TEST("nat-network-dns-srv-records");
|
|
DO_TEST("nat-network-dns-srv-record-minimal");
|
|
DO_TEST("nat-network-dns-hosts");
|
|
DO_TEST("nat-network-dns-forward-plain");
|
|
DO_TEST("nat-network-dns-forwarders");
|
|
DO_TEST("nat-network-dns-forwarder-no-resolv");
|
|
DO_TEST("nat-network-dns-local-domain");
|
|
DO_TEST("nat-network-forward-nat-ipv6");
|
|
DO_TEST("nat-network-forward-nat-address");
|
|
DO_TEST("nat-network-forward-nat-no-address");
|
|
DO_TEST("nat-network-name-with-quotes");
|
|
DO_TEST("nat-network-mtu");
|
|
DO_TEST("8021Qbh-net");
|
|
DO_TEST("direct-net");
|
|
DO_TEST("host-bridge-net");
|
|
DO_TEST("vepa-net");
|
|
DO_TEST("bandwidth-network");
|
|
DO_TEST("openvswitch-net");
|
|
DO_TEST_VALIDATE_ERROR("passthrough-pf");
|
|
#ifdef __linux__
|
|
DO_TEST("hostdev");
|
|
#else
|
|
/* Our test runners call networkValidateTests() which for
|
|
* <forward mode='hostdev'/> means validating that PCI
|
|
* devices are VFs. It's done so by querying sysfs which
|
|
* obviously works on Linux only. Thus, expect a validation
|
|
* error elsewhere. */
|
|
DO_TEST_VALIDATE_ERROR("hostdev");
|
|
#endif
|
|
DO_TEST_FLAGS("hostdev-pf", VIR_NETWORK_XML_INACTIVE);
|
|
DO_TEST_FLAGS("hostdev-pf-driver-model", VIR_NETWORK_XML_INACTIVE);
|
|
DO_TEST("ptr-domains-auto");
|
|
DO_TEST_VALIDATE_ERROR("passthrough-address-crash");
|
|
DO_TEST("nat-network-explicit-flood");
|
|
DO_TEST("host-bridge-no-flood");
|
|
DO_TEST_PARSE_ERROR("hostdev-duplicate");
|
|
DO_TEST_PARSE_ERROR("passthrough-duplicate");
|
|
DO_TEST("metadata");
|
|
DO_TEST("set-mtu");
|
|
DO_TEST("dnsmasq-options");
|
|
DO_TEST("leasetime-seconds");
|
|
DO_TEST("leasetime-minutes");
|
|
DO_TEST("leasetime-hours");
|
|
DO_TEST("leasetime-infinite");
|
|
DO_TEST("isolated-ports");
|
|
|
|
if (virTestCheckUnusedTestCases(existingTestCases) < 0)
|
|
ret = -1;
|
|
|
|
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
}
|
|
|
|
VIR_TEST_MAIN_PRELOAD(mymain,
|
|
VIR_TEST_MOCK("virpci"),
|
|
VIR_TEST_MOCK("virdnsmasq"),
|
|
VIR_TEST_MOCK("virrandom"))
|