From nobody Fri Dec 27 02:05:08 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=fail(p=none dis=none) header.from=canonical.com Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1731092699565168.58511701030443; Fri, 8 Nov 2024 11:04:59 -0800 (PST) Received: by lists.libvirt.org (Postfix, from userid 996) id 66A1717AF; Fri, 8 Nov 2024 14:04:58 -0500 (EST) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id 64EF2177C; Fri, 8 Nov 2024 14:04:26 -0500 (EST) Received: by lists.libvirt.org (Postfix, from userid 996) id 2DC2F176D; Fri, 8 Nov 2024 14:04:24 -0500 (EST) Received: from smtp-relay-internal-1.canonical.com (smtp-relay-internal-1.canonical.com [185.125.188.123]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id BDA021379 for ; Fri, 8 Nov 2024 14:04:22 -0500 (EST) Received: from mail-pf1-f200.google.com (mail-pf1-f200.google.com [209.85.210.200]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp-relay-internal-1.canonical.com (Postfix) with ESMTPS id C7BA73F2B5 for ; Fri, 8 Nov 2024 18:58:43 +0000 (UTC) Received: by mail-pf1-f200.google.com with SMTP id d2e1a72fcca58-71e8641f2a7so2513908b3a.2 for ; Fri, 08 Nov 2024 10:58:43 -0800 (PST) Received: from georgia.. ([2001:1284:f502:1e17:c852:ff87:4d41:d5e6]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-724078a7c76sm4157392b3a.48.2024.11.08.10.58.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 08 Nov 2024 10:58:40 -0800 (PST) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=DKIM_INVALID,DKIM_SIGNED, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=3.4.4 X-Greylist: delayed 336 seconds by postgrey-1.37 at lists.libvirt.org; Fri, 08 Nov 2024 14:04:22 EST DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=canonical.com; s=20210705; t=1731092323; bh=sYpcaV7lsHh/Tgm+/HnNC85l9JzW6hiEYdv91UUMjxo=; h=From:To:Cc:Subject:Date:Message-Id:MIME-Version; b=ZSTerOVf2h+QOreM0PNJbw8MvX28H/J8hitAxVv6a274xdKW1F4gawf+x7IHxmrtp QksYaUB8jQONjEXtc08aa8s/pWWo9RoR9r6QLHFjZ51FFsgvojWJ5CnZrelhyQ4mfj gh8Y7tNaWxhl0qjCo14v6S3lbs6ZzWtO6bORahn3te86+gP00ympjbyI1O0N6Wd6DX iaVmZow1/iilovfy2LqvzWn2qY0ZOvkQJGeQ00VD6iLtNgvJF7DIkUWE8UesG6m3/r zIpCQ2SKgrXp9C6NXGUGnOSTjAWt652mPwkbVsX48UDje+90P5K5CYsRBIf+6U7gs+ MQoquC3q02qWQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1731092322; x=1731697122; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=sYpcaV7lsHh/Tgm+/HnNC85l9JzW6hiEYdv91UUMjxo=; b=OQwbhV2r1f0zVh5Dm+PKYCrqC4z2wtgpFlRpXYUFObMkPFdQnE51oBi6vCYDRI7fHW o2d5kL30C4h/Z8HXwTHkQov0qQ5Zv+gHlfONsG8Qn6TnfW77mP0mLx0hKVsH6bMwrhDQ LajVLuuHkNut1LfnRIACjy9b32UdiAA1IRWU+8DdNVG22Ew2Ag1LVR4P4GltU/TAF7H6 FVPwu2yonxCvUOoNqciC3NIufsGxaakroe/CqF9lM1r2ovVsMysKOm3fJmA8QWUObsPX HL/gIdddII7Gh20cgWcR2Nn2AMlkKByUcbVJlIkSaN4pYiNMJZFJTgM88xMJ+iIQQMIi giRQ== X-Gm-Message-State: AOJu0YwAlirB4hUQDvh5qev0u7g2bMGd8docvJkxGKUhCTW82bskF7VZ yGMWvsLyYKqdGdaADzduIKxJBFBqCg+RAdcx+5hpW/bsV6N4WMs309JjxsADaVUoK9SqsCQ9nsO 4rw2VocucY91wCOs2kJeStBXJ75EafB9n0DzUpcQgG4lqreeojbVC2qNT9q8fa6kWKn1TPmc0bG ybYzdD7Q== X-Received: by 2002:a05:6a20:3d89:b0:1db:e10b:8a9a with SMTP id adf61e73a8af0-1dc22827457mr5664561637.6.1731092321820; Fri, 08 Nov 2024 10:58:41 -0800 (PST) X-Google-Smtp-Source: AGHT+IEe+QFsm82+1mRRRzGfy6xOquwiLyjsmRLoHlq+oOl6vY36FxtU7sFiy3IQdAUQmf1XgwrvnQ== X-Received: by 2002:a05:6a20:3d89:b0:1db:e10b:8a9a with SMTP id adf61e73a8af0-1dc22827457mr5664530637.6.1731092321407; Fri, 08 Nov 2024 10:58:41 -0800 (PST) From: Georgia Garcia To: devel@lists.libvirt.org Subject: [PATCH] security_apparmor: store dynamically generated rules Date: Fri, 8 Nov 2024 15:58:35 -0300 Message-Id: <20241108185835.244712-1-georgia.garcia@canonical.com> X-Mailer: git-send-email 2.34.1 MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-ID-Hash: HVROKRTU75D7242OX2XNQNVTASD7OLFO X-Message-ID-Hash: HVROKRTU75D7242OX2XNQNVTASD7OLFO X-MailFrom: georgia.garcia@canonical.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; header-match-config-2; header-match-config-3; header-match-devel.lists.libvirt.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header CC: Georgia Garcia X-Mailman-Version: 3.2.2 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-ZohoMail-DKIM: fail (Computed bodyhash is different from the expected one) X-ZM-MESSAGEID: 1731092702141116600 Content-Type: text/plain; charset="utf-8" Some rules are generated dynamically during boot and added to the AppArmor policy. An example of that is macvtap devices that call the AppArmorSetFDLabel hook to add a rule for the tap device path. Since this information is dynamic, it is not available in the xml config, therefore whenever a "Restore" hook is called, the entire profile is regenerated by virt-aa-helper based only the information from the VM definition, so the dynamic information is lost. This patch stores the dynamically generated rules while the domain is running and reloads them whenever there's a restore operation. Note that there are no hooks for restoring FD labels, so that information is not removed from the set of rules while the domain is running. Closes: https://gitlab.com/libvirt/libvirt/-/issues/692 Signed-off-by: Georgia Garcia --- src/security/security_apparmor.c | 208 +++++++++++++++++++++++++++++-- 1 file changed, 200 insertions(+), 8 deletions(-) diff --git a/src/security/security_apparmor.c b/src/security/security_appar= mor.c index 07e95ec81d..ae5815fb0d 100644 --- a/src/security/security_apparmor.c +++ b/src/security/security_apparmor.c @@ -60,6 +60,24 @@ struct SDPDOP { virDomainDef *def; }; =20 +typedef struct _virSecurityAppArmorLabel virSecurityAppArmorLabel; +struct _virSecurityAppArmorLabel { + GPtrArray *paths; + char *name; +}; + +typedef struct _virSecurityAppArmorLabelList virSecurityAppArmorLabelList; +struct _virSecurityAppArmorLabelList { + GPtrArray *labels; + virMutex lock; +}; + +virSecurityAppArmorLabelList *labelList =3D NULL; + +static int AppArmorRestorePolicy(virSecurityManager *mgr, + virDomainDef *def, + char *seclabel); + /* * profile_status returns '-2' on error, '-1' if not loaded, '0' if loaded * @@ -273,6 +291,119 @@ reload_profile(virSecurityManager *mgr, secdef->imagelabel); return -1; } + if (!append) { + if (AppArmorRestorePolicy(mgr, def, secdef->imagelabel) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot restore rules from AppArmor profi= le \'%1$s\'"), + secdef->imagelabel); + return -1; + } + } + + } + return 0; +} + +static int +AppArmorInitLabelList(void) +{ + if (labelList) + return 0; + + labelList =3D g_new0(virSecurityAppArmorLabelList, 1); + labelList->labels =3D g_ptr_array_new(); + if (virMutexInit(&labelList->lock) < 0) { + virReportSystemError(errno, "%s", + _("Failed to initialize AppArmor mutex")); + return -1; + } + return 0; +} + +static void +AppArmorFreeLabelList(void) +{ + size_t i; + if (!labelList) + return; + + VIR_WITH_MUTEX_LOCK_GUARD(&labelList->lock) { + for (i =3D 0; i < labelList->labels->len; i++) { + virSecurityAppArmorLabel *label =3D g_ptr_array_index(labelLis= t->labels, i); + g_ptr_array_free(label->paths, TRUE); + g_free(label->name); + } + g_ptr_array_free(labelList->labels, TRUE); + } + virMutexDestroy(&labelList->lock); + g_free(labelList); + labelList =3D NULL; +} + +static int +AppArmorAppendPathToLabelList(char *seclabel, + const char *path) +{ + size_t i; + char *new; + + if (!path) + return 0; + + if (!labelList) { + if (AppArmorInitLabelList() < 0) + return -1; + } + + VIR_WITH_MUTEX_LOCK_GUARD(&labelList->lock) { + for (i =3D 0; i < labelList->labels->len; i++) { + virSecurityAppArmorLabel *label =3D g_ptr_array_index(labelLis= t->labels, i); + if (STREQ(seclabel, label->name)) { + new =3D g_strdup(path); + g_ptr_array_add(label->paths, new); + } + } + } + return 0; +} + +static int +AppArmorLoadStoredPath(virSecurityAppArmorLabel *label, + virSecurityManager *mgr, + virDomainDef *def, + char *seclabel) +{ + size_t i; + for (i =3D 0; i < label->paths->len; i++) { + if (load_profile(mgr, seclabel, def, g_ptr_array_index(label->path= s, i), true) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot update AppArmor profile \'%1$s\' \'%2= $s\'"), + seclabel, (char *)g_ptr_array_index(label->path= s, i)); + return -1; + } + } + return 0; +} + +static int +AppArmorRestorePolicy(virSecurityManager *mgr, + virDomainDef *def, + char *seclabel) +{ + size_t i; + + if (!labelList) { + return 0; + } + + VIR_WITH_MUTEX_LOCK_GUARD(&labelList->lock) { + for (i =3D 0; i < labelList->labels->len; i++) { + virSecurityAppArmorLabel *label =3D g_ptr_array_index(labelLis= t->labels, i); + if (STREQ(seclabel, label->name)) { + if (AppArmorLoadStoredPath(label, mgr, def, seclabel) < 0) + return -1; + } + } } return 0; } @@ -330,12 +461,13 @@ AppArmorSecurityManagerProbe(const char *virtDriver G= _GNUC_UNUSED) static int AppArmorSecurityManagerOpen(virSecurityManager *mgr G_GNUC_UNUSED) { - return 0; + return AppArmorInitLabelList(); } =20 static int AppArmorSecurityManagerClose(virSecurityManager *mgr G_GNUC_UNUSED) { + AppArmorFreeLabelList(); return 0; } =20 @@ -364,6 +496,7 @@ AppArmorGenSecurityLabel(virSecurityManager *mgr G_GNUC= _UNUSED, g_autofree char *profile_name =3D NULL; virSecurityLabelDef *secdef =3D virDomainDefGetSecurityLabelDef(def, SECURITY_APPARMOR_NAME); + virSecurityAppArmorLabel *label; =20 if (!secdef) return 0; @@ -404,6 +537,18 @@ AppArmorGenSecurityLabel(virSecurityManager *mgr G_GNU= C_UNUSED, goto err; } =20 + if (!labelList) { + if (AppArmorInitLabelList() < 0) + goto err; + } + + label =3D g_new0(virSecurityAppArmorLabel, 1); + label->paths =3D g_ptr_array_new(); + label->name =3D g_strdup(profile_name); + VIR_WITH_MUTEX_LOCK_GUARD(&labelList->lock) { + g_ptr_array_add(labelList->labels, label); + } + return 0; =20 err: @@ -421,6 +566,7 @@ AppArmorSetSecurityAllLabel(virSecurityManager *mgr, bool chardevStdioLogd G_GNUC_UNUSED, bool migrated G_GNUC_UNUSED) { + int rc; virSecurityLabelDef *secdef =3D virDomainDefGetSecurityLabelDef(def, SECURITY_APPARMOR_NAME= ); if (!secdef || !secdef->relabel) @@ -428,8 +574,15 @@ AppArmorSetSecurityAllLabel(virSecurityManager *mgr, =20 /* Reload the profile if incomingPath is specified. Note that GenSecurityLabel() will have already been run. */ - if (incomingPath) - return reload_profile(mgr, def, incomingPath, true); + if (incomingPath) { + rc =3D reload_profile(mgr, def, incomingPath, true); + if (AppArmorAppendPathToLabelList(secdef->imagelabel, incomingPath= ) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("Could not store path")); + return -1; + } + return rc; + } =20 return 0; } @@ -495,6 +648,7 @@ AppArmorRestoreSecurityAllLabel(virSecurityManager *mgr= G_GNUC_UNUSED, bool migrated G_GNUC_UNUSED, bool chardevStdioLogd G_GNUC_UNUSED) { + size_t i; int rc =3D 0; virSecurityLabelDef *secdef =3D virDomainDefGetSecurityLabelDef(def, SECURITY_APPARMOR_NAME); @@ -508,6 +662,21 @@ AppArmorRestoreSecurityAllLabel(virSecurityManager *mg= r G_GNUC_UNUSED, _("could not remove profile for \'%1$s\'"), secdef->label); } + if (labelList) { + VIR_WITH_MUTEX_LOCK_GUARD(&labelList->lock) { + for (i =3D 0; i < labelList->labels->len; i++) { + virSecurityAppArmorLabel *label; + label =3D g_ptr_array_index(labelList->labels, i); + if (STREQ(secdef->label, label->name)) { + g_free(label->name); + g_ptr_array_free(label->paths, TRUE); + g_ptr_array_remove_index(labelList->labels, i); + break; + } + } + } + } + } return rc; } @@ -1082,15 +1251,26 @@ AppArmorSetPathLabel(virSecurityManager *mgr, { int rc =3D -1; char *full_path =3D NULL; + virSecurityLabelDef *secdef; + + secdef =3D virDomainDefGetSecurityLabelDef(def, SECURITY_APPARMOR_NAME= ); =20 if (allowSubtree) { full_path =3D g_strdup_printf("%s/{,**}", path); - rc =3D reload_profile(mgr, def, full_path, true); - VIR_FREE(full_path); } else { - rc =3D reload_profile(mgr, def, path, true); + full_path =3D g_strdup(path); + } + + rc =3D reload_profile(mgr, def, full_path, true); + if (rc =3D=3D 0) { + if (AppArmorAppendPathToLabelList(secdef->imagelabel, full_path) <= 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("Could not store path")); + rc =3D -1; + } } =20 + VIR_FREE(full_path); return rc; } =20 @@ -1107,6 +1287,7 @@ AppArmorSetFDLabel(virSecurityManager *mgr, virDomainDef *def, int fd) { + int rc =3D 0; char *proc =3D NULL; char *fd_path =3D NULL; =20 @@ -1121,10 +1302,21 @@ AppArmorSetFDLabel(virSecurityManager *mgr, if (virFileResolveLink(proc, &fd_path) < 0) { /* it's a deleted file, presumably. Ignore? */ VIR_WARN("could not find path for descriptor %s, skipping", proc); - return 0; + goto err; } =20 - return reload_profile(mgr, def, fd_path, true); + rc =3D reload_profile(mgr, def, fd_path, true); + if (rc =3D=3D 0) { + if (AppArmorAppendPathToLabelList(secdef->imagelabel, fd_path) < 0= ) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("Could not store path")); + rc =3D -1; + } + } + g_free(fd_path); + err: + g_free(proc); + return rc; } =20 static char * --=20 2.34.1