[PATCH v4 0/3] apparmor: Preserve macvtap path in domain profile
I will open a separate issue for tracking the blockcommit r/w permissions side of this (as I should have done all along). I've opened a MR to libvirt-tck with a test case that demonstrates the bug [1]. apparmor/110-macvtap.t passes with these patches applied. Thanks for the reviews and continued consideration. [1] https://gitlab.com/libvirt/libvirt-tck/-/merge_requests/73 Resolves: https://gitlab.com/libvirt/libvirt/-/issues/692 Signed-off-by: Wesley Hershberger <wesley.hershberger@canonical.com> --- Changes in v4: - Split apparmor changes to separate patches - virBufferEscapeString for formatting in XML - Fix dangling pointer in virNetDevMacVLanTapOpen - Added tapfd path to qemustatusxml2xmldata Changes in v3: - Fix buglink in commit message - Link to v2: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/IPEBL... Changes in v2: - Drop `virt-aa-helper: Ask for no deny rule...` as it was applied - Drop `qemu: Store blockcommit permissions...` due to unresolved concerns - Pass tapfd path through netdef instead of resolving from fd - Link to v1: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/UNNBQ... --- Wesley Hershberger (3): qemu: Store tapfd path in domstatus XML apparmor: Pass status XML to virt-aa-helper virt-aa-helper: Include macvtap tapfd path src/conf/domain_conf.c | 8 ++++++++ src/conf/domain_conf.h | 1 + src/hypervisor/domain_interface.c | 2 +- src/lxc/lxc_process.c | 1 + src/qemu/qemu_interface.c | 1 + src/security/security_apparmor.c | 1 + src/security/virt-aa-helper.c | 5 +++++ src/util/virnetdevmacvlan.c | 18 +++++++++++------- src/util/virnetdevmacvlan.h | 4 +++- tests/qemustatusxml2xmldata/modern-in.xml | 7 +++++++ 10 files changed, 39 insertions(+), 9 deletions(-) --- base-commit: 792cb6bf60e774ee8ecf9e7d3cd2b6f21011ab43 change-id: 20260105-apparmor-races-d03238ee4d93 Best regards, -- Wesley Hershberger <wesley.hershberger@canonical.com>
Introduce a read-only `tapfd` element for direct interfaces (macvtap), which contains the path to the backing tapfd for that interface (e.g. `/dev/tapXX`). The element is only included when the domain is being formatted for internal consumption (VIR_DOMAIN_DEF_FORMAT_STATUS) and is not accepted in user-provided XML (!VIR_DOMAIN_DEF_PARSE_INACTIVE). This will be used by the AppArmor security driver when re-generating profiles. Reviewed-by: Peter Krempa <pkrempa@redhat.com> Signed-off-by: Wesley Hershberger <wesley.hershberger@canonical.com> --- src/conf/domain_conf.c | 8 ++++++++ src/conf/domain_conf.h | 1 + src/hypervisor/domain_interface.c | 2 +- src/lxc/lxc_process.c | 1 + src/qemu/qemu_interface.c | 1 + src/util/virnetdevmacvlan.c | 18 +++++++++++------- src/util/virnetdevmacvlan.h | 4 +++- tests/qemustatusxml2xmldata/modern-in.xml | 7 +++++++ 8 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index b1ee519ff6..3497e84bf5 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -2971,6 +2971,7 @@ virDomainNetDefFree(virDomainNetDef *def) g_free(def->virtio); g_free(def->coalesce); g_free(def->sourceDev); + g_free(def->tapfdpath); virNetDevIPInfoClear(&def->guestIP); virNetDevIPInfoClear(&def->hostIP); @@ -10635,6 +10636,10 @@ virDomainNetDefParseXML(virDomainXMLOption *xmlopt, return NULL; } + if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)) { + def->tapfdpath = virXPathString("string(./tapfd/@path)", ctxt); + } + if (virNetworkPortOptionsParseXML(ctxt, &def->isolatedPort) < 0) return NULL; @@ -26032,6 +26037,9 @@ virDomainNetDefFormat(virBuffer *buf, if (def->mtu) virBufferAsprintf(buf, "<mtu size='%u'/>\n", def->mtu); + if (def->tapfdpath && (flags & VIR_DOMAIN_DEF_FORMAT_STATUS)) + virBufferEscapeString(buf, "<tapfd path='%s'/>\n", def->tapfdpath); + virDomainNetDefCoalesceFormatXML(buf, def->coalesce); virDomainDeviceInfoFormat(buf, &def->info, flags | VIR_DOMAIN_DEF_FORMAT_ALLOW_BOOT diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 75acfc46bf..a8f90803da 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1212,6 +1212,7 @@ struct _virDomainNetDef { char *downscript; char *domain_name; /* backend domain name */ char *ifname; /* interface name on the host (<target dev='x'/>) */ + char *tapfdpath; /* Path in /dev for macvtap (<tapfd path='/dev/tapXX'/>) */ virTristateBool managed_tap; virNetDevIPInfo hostIP; char *ifname_guest_actual; diff --git a/src/hypervisor/domain_interface.c b/src/hypervisor/domain_interface.c index 5bc698d272..37e3d453a0 100644 --- a/src/hypervisor/domain_interface.c +++ b/src/hypervisor/domain_interface.c @@ -111,7 +111,7 @@ virDomainInterfaceEthernetConnect(virDomainDef *def, if (virNetDevMacVLanIsMacvtap(net->ifname)) { auditdev = net->ifname; - if (virNetDevMacVLanTapOpen(net->ifname, tapfd, tapfdSize) < 0) + if (virNetDevMacVLanTapOpen(net->ifname, tapfd, tapfdSize, &net->tapfdpath) < 0) goto cleanup; if (virNetDevMacVLanTapSetup(tapfd, tapfdSize, virDomainInterfaceIsVnetCompatModel(net)) < 0) { diff --git a/src/lxc/lxc_process.c b/src/lxc/lxc_process.c index 1bca9e8dae..c731b28871 100644 --- a/src/lxc/lxc_process.c +++ b/src/lxc/lxc_process.c @@ -379,6 +379,7 @@ virLXCProcessSetupInterfaceDirect(virLXCDriver *driver, VIR_NETDEV_VPORT_PROFILE_OP_CREATE, cfg->stateDir, NULL, 0, + &net->tapfdpath, macvlan_create_flags) < 0) return NULL; diff --git a/src/qemu/qemu_interface.c b/src/qemu/qemu_interface.c index 23a23d201a..edc53d53b3 100644 --- a/src/qemu/qemu_interface.c +++ b/src/qemu/qemu_interface.c @@ -81,6 +81,7 @@ qemuInterfaceDirectConnect(virDomainDef *def, &res_ifname, vmop, cfg->stateDir, tapfd, tapfdSize, + &net->tapfdpath, macvlan_create_flags) < 0) goto cleanup; diff --git a/src/util/virnetdevmacvlan.c b/src/util/virnetdevmacvlan.c index cde9d70eef..07ccef52d9 100644 --- a/src/util/virnetdevmacvlan.c +++ b/src/util/virnetdevmacvlan.c @@ -152,24 +152,24 @@ int virNetDevMacVLanDelete(const char *ifname) int virNetDevMacVLanTapOpen(const char *ifname, int *tapfd, - size_t tapfdSize) + size_t tapfdSize, + char **tapname) { int retries = 10; int ret = -1; int ifindex; size_t i = 0; - g_autofree char *tapname = NULL; if (virNetDevGetIndex(ifname, &ifindex) < 0) return -1; - tapname = g_strdup_printf("/dev/tap%d", ifindex); + *tapname = g_strdup_printf("/dev/tap%d", ifindex); for (i = 0; i < tapfdSize; i++) { int fd = -1; while (fd < 0) { - if ((fd = open(tapname, O_RDWR)) >= 0) { + if ((fd = open(*tapname, O_RDWR)) >= 0) { tapfd[i] = fd; } else if (retries-- > 0) { /* may need to wait for udev to be done */ @@ -178,7 +178,7 @@ virNetDevMacVLanTapOpen(const char *ifname, /* However, if haven't succeeded, quit. */ virReportSystemError(errno, _("cannot open macvtap tap device %1$s"), - tapname); + *tapname); goto cleanup; } } @@ -188,6 +188,7 @@ virNetDevMacVLanTapOpen(const char *ifname, cleanup: if (ret < 0) { + g_clear_pointer(tapname, g_free); while (i--) VIR_FORCE_CLOSE(tapfd[i]); } @@ -659,6 +660,7 @@ virNetDevMacVLanCreateWithVPortProfile(const char *ifnameRequested, char *stateDir, int *tapfd, size_t tapfdSize, + char **tapfdpath, unsigned int flags) { g_autofree char *ifname = NULL; @@ -729,7 +731,7 @@ virNetDevMacVLanCreateWithVPortProfile(const char *ifnameRequested, } if (flags & VIR_NETDEV_MACVLAN_CREATE_WITH_TAP) { - if (virNetDevMacVLanTapOpen(ifname, tapfd, tapfdSize) < 0) + if (virNetDevMacVLanTapOpen(ifname, tapfd, tapfdSize, tapfdpath) < 0) goto disassociate_exit; if (virNetDevMacVLanTapSetup(tapfd, tapfdSize, vnet_hdr) < 0) @@ -888,7 +890,8 @@ int virNetDevMacVLanDelete(const char *ifname G_GNUC_UNUSED) int virNetDevMacVLanTapOpen(const char *ifname G_GNUC_UNUSED, int *tapfd G_GNUC_UNUSED, - size_t tapfdSize G_GNUC_UNUSED) + size_t tapfdSize G_GNUC_UNUSED, + char **tapname G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Cannot create macvlan devices on this platform")); @@ -917,6 +920,7 @@ int virNetDevMacVLanCreateWithVPortProfile(const char *ifname G_GNUC_UNUSED, char *stateDir G_GNUC_UNUSED, int *tapfd G_GNUC_UNUSED, size_t tapfdSize G_GNUC_UNUSED, + char **tapfdpath G_GNUC_UNUSED, unsigned int unused_flags G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", diff --git a/src/util/virnetdevmacvlan.h b/src/util/virnetdevmacvlan.h index 31e4804cdc..7424b87965 100644 --- a/src/util/virnetdevmacvlan.h +++ b/src/util/virnetdevmacvlan.h @@ -72,13 +72,15 @@ int virNetDevMacVLanCreateWithVPortProfile(const char *ifname, char *stateDir, int *tapfd, size_t tapfdSize, + char **tapfdpath, unsigned int flags) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(6) ATTRIBUTE_NONNULL(8) ATTRIBUTE_NONNULL(10) G_GNUC_WARN_UNUSED_RESULT; int virNetDevMacVLanTapOpen(const char *ifname, int *tapfd, - size_t tapfdSize) + size_t tapfdSize, + char **tapname) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) G_GNUC_WARN_UNUSED_RESULT; diff --git a/tests/qemustatusxml2xmldata/modern-in.xml b/tests/qemustatusxml2xmldata/modern-in.xml index 3b3e831759..050669f554 100644 --- a/tests/qemustatusxml2xmldata/modern-in.xml +++ b/tests/qemustatusxml2xmldata/modern-in.xml @@ -437,6 +437,13 @@ <alias name='net0'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> </interface> + <interface type='direct'> + <mac address='52:54:00:36:bd:3c'/> + <source dev='eth0' mode='vepa'/> + <model type='rtl8139'/> + <tapfd path='/dev/tap8'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0d' function='0x0'/> + </interface> <serial type='pty'> <source path='/dev/pts/67'/> <target type='isa-serial' port='0'> -- 2.53.0
On Mon, Apr 13, 2026 at 10:23:45 -0500, Wesley Hershberger wrote:
Introduce a read-only `tapfd` element for direct interfaces (macvtap), which contains the path to the backing tapfd for that interface (e.g. `/dev/tapXX`).
The element is only included when the domain is being formatted for internal consumption (VIR_DOMAIN_DEF_FORMAT_STATUS) and is not accepted in user-provided XML (!VIR_DOMAIN_DEF_PARSE_INACTIVE).
This will be used by the AppArmor security driver when re-generating profiles.
Reviewed-by: Peter Krempa <pkrempa@redhat.com> Signed-off-by: Wesley Hershberger <wesley.hershberger@canonical.com> --- src/conf/domain_conf.c | 8 ++++++++ src/conf/domain_conf.h | 1 + src/hypervisor/domain_interface.c | 2 +- src/lxc/lxc_process.c | 1 + src/qemu/qemu_interface.c | 1 + src/util/virnetdevmacvlan.c | 18 +++++++++++------- src/util/virnetdevmacvlan.h | 4 +++- tests/qemustatusxml2xmldata/modern-in.xml | 7 +++++++ 8 files changed, 33 insertions(+), 9 deletions(-)
[...]
diff --git a/src/util/virnetdevmacvlan.c b/src/util/virnetdevmacvlan.c index cde9d70eef..07ccef52d9 100644 --- a/src/util/virnetdevmacvlan.c +++ b/src/util/virnetdevmacvlan.c @@ -152,24 +152,24 @@ int virNetDevMacVLanDelete(const char *ifname) int virNetDevMacVLanTapOpen(const char *ifname, int *tapfd, - size_t tapfdSize) + size_t tapfdSize, + char **tapname) { int retries = 10; int ret = -1; int ifindex; size_t i = 0; - g_autofree char *tapname = NULL;
if (virNetDevGetIndex(ifname, &ifindex) < 0) return -1;
- tapname = g_strdup_printf("/dev/tap%d", ifindex); + *tapname = g_strdup_printf("/dev/tap%d", ifindex);
for (i = 0; i < tapfdSize; i++) { int fd = -1;
while (fd < 0) { - if ((fd = open(tapname, O_RDWR)) >= 0) { + if ((fd = open(*tapname, O_RDWR)) >= 0) { tapfd[i] = fd; } else if (retries-- > 0) { /* may need to wait for udev to be done */ @@ -178,7 +178,7 @@ virNetDevMacVLanTapOpen(const char *ifname, /* However, if haven't succeeded, quit. */ virReportSystemError(errno, _("cannot open macvtap tap device %1$s"), - tapname); + *tapname); goto cleanup;
The two hunks above had a conflict with recently merged\ e52ca27026c08d1bee48fcb63ec717ef96d7911ae although it was straightforward to address
} }
On Tue, Apr 14, 2026 at 4:53 AM Peter Krempa <pkrempa@redhat.com> wrote:
The two hunks above had a conflict with recently merged\ e52ca27026c08d1bee48fcb63ec717ef96d7911ae although it was straightforward to address
Thanks so much for your help with this fix! ~Wesley
VIR_DOMAIN_DEF_FORMAT_STATUS is used to include disk & network privateData elements in the domain XML, which contain misc information that should be available to the virt-aa-helper when generating rules. For now, this will be used in a subsequent patch to pass tap paths to the virt-aa-helper. Reviewed-by: Peter Krempa <pkrempa@redhat.com> Signed-off-by: Wesley Hershberger <wesley.hershberger@canonical.com> --- src/security/security_apparmor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/security/security_apparmor.c b/src/security/security_apparmor.c index e53486ee0c..a66382fbac 100644 --- a/src/security/security_apparmor.c +++ b/src/security/security_apparmor.c @@ -156,6 +156,7 @@ load_profile(virSecurityManager *mgr G_GNUC_UNUSED, if (virDomainDefFormatInternal(def, NULL, &buf, VIR_DOMAIN_DEF_FORMAT_SECURE | + VIR_DOMAIN_DEF_FORMAT_STATUS | VIR_DOMAIN_DEF_FORMAT_VOLUME_TRANSLATED) < 0) return -1; -- 2.53.0
Wthout this change, the tapfd path would only be appended to a domain's profile when the device is hotplugged (either during domain start or normal operation). Operations which regenerate the profile (blockcommit, etc) will cause this path to be dropped from the profile. Since the domain status XML now includes the path to the tap device, include it in the profile. Resolves: https://gitlab.com/libvirt/libvirt/-/issues/692 Bug-Ubuntu: https://bugs.launchpad.net/bugs/2126574 Reviewed-by: Peter Krempa <pkrempa@redhat.com> Signed-off-by: Wesley Hershberger <wesley.hershberger@canonical.com> --- src/security/virt-aa-helper.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/security/virt-aa-helper.c b/src/security/virt-aa-helper.c index 14b202bf7b..2eae4d7f3f 100644 --- a/src/security/virt-aa-helper.c +++ b/src/security/virt-aa-helper.c @@ -1192,6 +1192,11 @@ get_files(vahControl * ctl) vhu->type) != 0) return -1; } + + if (net->tapfdpath) { + if (vah_add_file(&buf, net->tapfdpath, "rwk") != 0) + return -1; + } } for (i = 0; i < ctl->def->nmems; i++) { -- 2.53.0
participants (2)
-
Peter Krempa -
Wesley Hershberger