[PATCH v3] qemu: Store tapfd path in domstatus XML
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 is used by the AppArmor security driver when re-generating profiles. Partial-Resolves: https://gitlab.com/libvirt/libvirt/-/issues/692 Bug-Ubuntu: https://bugs.launchpad.net/bugs/2126574 Signed-off-by: Wesley Hershberger <wesley.hershberger@canonical.com> --- Resending this patch as I've not recieved a response on my previous submission. Fixed the bug URL in the commit message as I missed that feedback item on my last mail. This submission is a partial revision of a previous series with a fix for the macvtap component of gitlab#692 [1][2]. I haven't had bandwidth to resolve the blockcommit component since the complexity there is somewhat higher (and is also lower priority for us). I kept the separate `tapfd` element rather than reusing the existing `backend` element (virDomainNetBackend.tap) to avoid making a user-visible change [3]. I'd be happy to use the existing field instead if you think that would make more sense. I opted not to introduce/modify a security driver API for FD+path as the patch here is sufficient to resolve the bug, but would be willing to do so if that would make this change more palatable. I've opened a MR to libvirt-tck with test cases that demonstrate the bugs that this fixes [4]. apparmor/110-macvtap.t passes with this patch applied. Thanks for the reviews and continued consideration. ~Wesley [1] https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/UNNBQ... [2] https://gitlab.com/libvirt/libvirt/-/issues/692 [3] https://libvirt.org/formatdomain.html#setting-network-backend-specific-optio... [4] https://gitlab.com/libvirt/libvirt-tck/-/merge_requests/73 --- 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... --- 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 +++- 9 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 03a05366e1..9714a1e141 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); @@ -10634,6 +10635,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; @@ -26004,6 +26009,9 @@ virDomainNetDefFormat(virBuffer *buf, if (def->mtu) virBufferAsprintf(buf, "<mtu size='%u'/>\n", def->mtu); + if (def->tapfdpath && (flags & VIR_DOMAIN_DEF_FORMAT_STATUS)) + virBufferAsprintf(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 e63230beec..3bb4de6274 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/security/security_apparmor.c b/src/security/security_apparmor.c index 40f13ec1a5..3ff80e1dc9 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; diff --git a/src/security/virt-aa-helper.c b/src/security/virt-aa-helper.c index e932e79dab..60e03c2ce8 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++) { diff --git a/src/util/virnetdevmacvlan.c b/src/util/virnetdevmacvlan.c index cde9d70eef..fcf63e08ff 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_free(*tapname); 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; --- base-commit: 54f5032e576a886cfb8232abbfa08a2f6e9f9962 change-id: 20260105-apparmor-races-d03238ee4d93 Best regards, -- Wesley Hershberger <wesley.hershberger@canonical.com>
Hi all, On Tue, Mar 17, 2026 at 9:07 AM Wesley Hershberger <wesley.hershberger@canonical.com> wrote:
Introduce a read-only `tapfd` element for direct interfaces (macvtap),
...
--- Resending this patch as I've not recieved a response on my previous submission. Fixed the bug URL in the commit message as I missed that feedback item on my last mail.
...
It has now been two months since I submitted the v2 of this patch in response to feedback; I haven't received any response to it or the two follow-up mails since. I acknowledge that AppArmor is not the project's highest priority and that this is not the most palatable change, but it would be helpful for us to have some indication if there is *not* willingness to merge this patch. As it is, I'm left guessing at what the remaining concerns might be. There was a (very valid) point raised (several times) regarding the way that the driver AppArmor manages state (it doesn't), but I don't have the capacity to fix that problem in any kind of comprehensive way. I'm happy to address any additional feedback on this patch, but it is my sense that this solution meets the requirements for non-user-visibility described in the original thread [1] and improves the situation WRT the bug in question. Please let me know what the remaining blockers are so that I can address them. Thanks for your understanding, ~Wesley Hershberger Canonical Support [1] https://www.mail-archive.com/devel@lists.libvirt.org/msg07884.html
On Thu, Apr 02, 2026 at 15:07:21 -0500, Wesley Hershberger via Devel wrote:
Hi all,
On Tue, Mar 17, 2026 at 9:07 AM Wesley Hershberger <wesley.hershberger@canonical.com> wrote:
Introduce a read-only `tapfd` element for direct interfaces (macvtap),
...
--- Resending this patch as I've not recieved a response on my previous submission. Fixed the bug URL in the commit message as I missed that feedback item on my last mail.
...
It has now been two months since I submitted the v2 of this patch in response to feedback; I haven't received any response to it or the two follow-up mails since. I acknowledge that
I'm sorry it slipped through my review queue.
AppArmor is not the project's highest priority and that this is not the most palatable change, but it would be helpful for us to have some indication if there is
Priority or not, all patches are welcome. But patches need reviews. We do want to encourage anyone to review patches, even partially. I see you've CC'd some colleagues, encourage them to send review, it might get noticed.
*not* willingness to merge this patch. As it is, I'm left guessing at what the remaining concerns might be.a
I'll reply inline in the patch.
There was a (very valid) point raised (several times) regarding the way that the driver AppArmor manages state (it doesn't), but I don't have the capacity to fix that problem in any kind of comprehensive way. I'm happy to address any additional feedback on this patch, but it is my sense that this solution meets the requirements for non-user-visibility described in the original thread [1] and improves the situation WRT the bug in question.
It does.
Please let me know what the remaining blockers are so that I can address them.
Thanks for your understanding, ~Wesley Hershberger Canonical Support
[1] https://www.mail-archive.com/devel@lists.libvirt.org/msg07884.html
On Tue, Mar 17, 2026 at 09:06:55 -0500, Wesley Hershberger via Devel 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 is used by the AppArmor security driver when re-generating profiles.
Partial-Resolves: https://gitlab.com/libvirt/libvirt/-/issues/692 Bug-Ubuntu: https://bugs.launchpad.net/bugs/2126574 Signed-off-by: Wesley Hershberger <wesley.hershberger@canonical.com> --- Resending this patch as I've not recieved a response on my previous submission. Fixed the bug URL in the commit message as I missed that feedback item on my last mail.
This submission is a partial revision of a previous series with a fix for the macvtap component of gitlab#692 [1][2]. I haven't had bandwidth to resolve the blockcommit component since the complexity there is somewhat higher (and is also lower priority for us).
I kept the separate `tapfd` element rather than reusing the existing `backend` element (virDomainNetBackend.tap) to avoid making a user-visible change [3]. I'd be happy to use the existing field instead if you think that would make more sense.
I opted not to introduce/modify a security driver API for FD+path as the patch here is sufficient to resolve the bug, but would be willing to do so if that would make this change more palatable.
I've opened a MR to libvirt-tck with test cases that demonstrate the bugs that this fixes [4]. apparmor/110-macvtap.t passes with this patch applied.
Thanks for the reviews and continued consideration. ~Wesley
[1] https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/UNNBQ... [2] https://gitlab.com/libvirt/libvirt/-/issues/692 [3] https://libvirt.org/formatdomain.html#setting-network-backend-specific-optio... [4] https://gitlab.com/libvirt/libvirt-tck/-/merge_requests/73 --- 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... --- 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 +++-
This patch does a bit too much in one patch. It'll need to be split to the following parts: - plumbing of the tapfd into status XML - feeding apparmor the status XML - actual apparmor change to use the tapfd
9 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 03a05366e1..9714a1e141 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); @@ -10634,6 +10635,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;
@@ -26004,6 +26009,9 @@ virDomainNetDefFormat(virBuffer *buf, if (def->mtu) virBufferAsprintf(buf, "<mtu size='%u'/>\n", def->mtu);
+ if (def->tapfdpath && (flags & VIR_DOMAIN_DEF_FORMAT_STATUS)) + virBufferAsprintf(buf, "<tapfd path='%s'/>\n", def->tapfdpath);
use virBufferEscapeString to ensure the path is formatted with XML entities escaped Since it's an separate element, ideally add an example of the above to one of the test cases in tests/qemustatusxml2xmldata/. It'll prevent potential gotcha if anyone ever added such element. modern-in.xml/modern-out.xml is the catch-all for random newly added stuff without specific purpose.
+ virDomainNetDefCoalesceFormatXML(buf, def->coalesce);
virDomainDeviceInfoFormat(buf, &def->info, flags | VIR_DOMAIN_DEF_FORMAT_ALLOW_BOOT
diff --git a/src/security/security_apparmor.c b/src/security/security_apparmor.c index 40f13ec1a5..3ff80e1dc9 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;
diff --git a/src/security/virt-aa-helper.c b/src/security/virt-aa-helper.c index e932e79dab..60e03c2ce8 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++) {
The two hunks above belong to one or two separate patches based on how you'd want to justify it in the commit messages.
diff --git a/src/util/virnetdevmacvlan.c b/src/util/virnetdevmacvlan.c index cde9d70eef..fcf63e08ff 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_free(*tapname);
This leaves the pointer dangling in the return-via-argument. Use g_clear_pointer(tapname, g_free) instead to ensure it's NULL-ed out or assign to the return-via-argument only at success (e.g. via g_steal_pointer from the local variable).
while (i--) VIR_FORCE_CLOSE(tapfd[i]); }
With all of the requested changes, you can directly use: Reviewed-by: Peter Krempa <pkrempa@redhat.com> in the patches.
participants (2)
-
Peter Krempa -
Wesley Hershberger