[libvirt] Resubmission #2: [PATCH 0/3] sVirt AppArmor security driver

Resubmitting again based on feedback from this list. Notably, the driver has been reworked to use: xml = virDomainDefFormat(conn, vm->def, ... Then the helper program uses: ctl->def = virDomainDefParseString(... xmlStr ...); This simplifies adding new files. Part of this process involved adding support for kernel, initrd, loader, serial, and console. I have commented out code for hostdev (and pci would be easy enough), but it requires a patch to to move the _usbDevice struct fully into hostusb.h. I figure once the AppArmor driver is in, I can submit a patch for that. This iteration also properly works with attach/detach. While I use virDomainDefFormat() primarily, this does not work with attach because the definition is not updated with the information yet (neither def nor newDef), so I pass the disk file on the command line. For now this is ok since SetSecurityImageLabel currently only deals with a single file, but this will need to be adjusted depending on how the storage backend patch works out. I thought about creating a new def and inserting the disk into it and sending it off to virt-aa-helper, but I couldn't find an easy way to get an accurate virCapsPtr in security_apparmor.c. I had a similar problem with virt-aa-helper, but there I just created a fake virCapsPtr (which defaults to 'hvm', which was a compromise I was willing to make at this time since sVirt only supports qemu right now). The issues with virCapsPtr aside, the code is overall cleaner and much more easily extendable, so thanks for the excellent feedback. :) The patches are against up to date trunk, compiles with no warnings, 'make check' passes for the tests I added, and the code passes syntax-check. With the exception of the three calls to strncpy I had to convert to virStr*, this is the code that is currently in Ubuntu 9.10 (0.7.0-1ubuntu8). Among other things, that means 9.10 now has your preferred licensing. [PATCH 1] patch_1_reenable-nonfile-labels.patch (Updated based on prior feedback): When James Morris originally submitted his sVirt patches (as seen in libvirt 0.6.1), he did not require on disk labelling for virSecurityDomainRestoreImageLabel. A later commit[2] changed this behavior to assume on disk labelling, which halts implementations for path-based MAC systems such as AppArmor and TOMOYO where vm->def->seclabel is required to obtain the label. This patch simply adds the 'virDomainObjPtr vm' argument back to *RestoreImageLabel. [PATCH 2] patch_2_apparmor_driver.patch (updated and merged into one patch based on prior feedback): - Updates src/security.c for AppArmor - Adds security_apparmor.c, security_apparmor.h, virt-aa-helper.c and updates po/POTFILES.in. virt-aa-helper.c is a new binary which is used exclusively by the AppArmor security driver to manipulate AppArmor. - Adds tests for virt-aa-helper and the security driver. secaatest.c is identical to seclabeltest.c except it initializes the 'apparmor' driver instead of 'selinux'. These tests are integrated into 'make check' and pass. - Updates configure.in, Makefile.am, src/Makefile.am, tests/Makefile.am, and tools/Makefile.am for AppArmor. It is based on and should operate the same as the SELinux configuration. [PATCH 3] patch_3_docs.patch (Updated based on prior feedback): Updates docs/drvqemu.html.in for AppArmor and adds profile examples to examples/apparmor. Updated based on prior feedback. Jamie On Fri, 04 Sep 2009, Jamie Strandboge wrote:
This patch series implements the AppArmor security driver for sVirt. This implementation was developed for the Ubuntu AppArmorLibvirtProfile specification[1], but is general enough for any AppArmor deployment (such as Ubuntu, *SUSE and Mandriva).
This patch has seen quite a bit of real world testing in Ubuntu 9.10 (our development release) in our 0.7.0-1ubuntu3 package. I did make a few small changes after going through HACKING, but mostly I got the tests going and added documentation.
DESIGN ------ When a virtual machine is started, determine if a profile is currently defined for the machine, and use it if available. If not, generate a new profile for the machine based on a template, which is by default a very restrictive profile allowing access to disk files, and anything else needed to run, such as the pid, monitor and log files.
Virtual machines should have a unique profile specific to that machine. To ensure uniqueness, the profile name will be derived from the UUID of the virtual machine. These profiles should be configurable, either by adjusting the profile template for new machines, creating/modifying the VM profile directly or through the use of AppArmor abstractions. This will allow for administrators to fine-tune confinement for individual machines if desired.
If enabled at compile time, the sVirt security model will be activated if AppArmor is available on the host OS and a profile for the libvirtd daemon is loaded when libvirtd is started.
libvirtd should not be allowed to create arbitrary profiles or modify profiles directly, so as to not allow libvirtd to potentially (ie via a security bug in libvirtd itself) bootstrap out of AppArmor confinement.
Because root privileges are needed to manipulate AppArmor profiles, qemu:///session will not be supported at this time, but the implementation must allow for a confined libvirtd with qemu:///session guests running unconfined. This can be revisited when AppArmor supports per-user profiles.
Please see the specification[1] for more details.
-- Jamie Strandboge | http://www.canonical.com

On Fri, 25 Sep 2009, Jamie Strandboge wrote:
[PATCH 1] patch_1_reenable-nonfile-labels.patch (Updated based on prior feedback): When James Morris originally submitted his sVirt patches (as seen in libvirt 0.6.1), he did not require on disk labelling for virSecurityDomainRestoreImageLabel. A later commit[2] changed this behavior to assume on disk labelling, which halts implementations for path-based MAC systems such as AppArmor and TOMOYO where vm->def->seclabel is required to obtain the label. This patch simply adds the 'virDomainObjPtr vm' argument back to *RestoreImageLabel.
-- Jamie Strandboge | http://www.canonical.com

On Fri, Sep 25, 2009 at 05:47:35PM -0500, Jamie Strandboge wrote:
On Fri, 25 Sep 2009, Jamie Strandboge wrote:
[PATCH 1] patch_1_reenable-nonfile-labels.patch (Updated based on prior feedback): When James Morris originally submitted his sVirt patches (as seen in libvirt 0.6.1), he did not require on disk labelling for virSecurityDomainRestoreImageLabel. A later commit[2] changed this behavior to assume on disk labelling, which halts implementations for path-based MAC systems such as AppArmor and TOMOYO where vm->def->seclabel is required to obtain the label. This patch simply adds the 'virDomainObjPtr vm' argument back to *RestoreImageLabel.
-- Jamie Strandboge | http://www.canonical.com
diff -Naurp libvirt.orig/src/qemu/qemu_driver.c libvirt/src/qemu/qemu_driver.c --- libvirt.orig/src/qemu/qemu_driver.c 2009-09-25 10:50:21.000000000 -0500 +++ libvirt/src/qemu/qemu_driver.c 2009-09-25 16:56:32.000000000 -0500 @@ -6309,7 +6309,7 @@ static int qemudDomainDetachDevice(virDo dev->data.disk->bus == VIR_DOMAIN_DISK_BUS_VIRTIO)) { ret = qemudDomainDetachPciDiskDevice(dom->conn, vm, dev); if (driver->securityDriver) - driver->securityDriver->domainRestoreSecurityImageLabel(dom->conn, dev->data.disk); + driver->securityDriver->domainRestoreSecurityImageLabel(dom->conn, vm, dev->data.disk); if (qemuDomainSetDeviceOwnership(dom->conn, driver, dev, 1) < 0) VIR_WARN0("Fail to restore disk device ownership"); } else if (dev->type == VIR_DOMAIN_DEVICE_NET) { diff -Naurp libvirt.orig/src/security/security_driver.h libvirt/src/security/security_driver.h --- libvirt.orig/src/security/security_driver.h 2009-09-22 12:51:57.000000000 -0500 +++ libvirt/src/security/security_driver.h 2009-09-25 16:56:32.000000000 -0500 @@ -32,6 +32,7 @@ typedef virSecurityDriverStatus (*virSec typedef int (*virSecurityDriverOpen) (virConnectPtr conn, virSecurityDriverPtr drv); typedef int (*virSecurityDomainRestoreImageLabel) (virConnectPtr conn, + virDomainObjPtr vm, virDomainDiskDefPtr disk); typedef int (*virSecurityDomainSetImageLabel) (virConnectPtr conn, virDomainObjPtr vm, diff -Naurp libvirt.orig/src/security/security_selinux.c libvirt/src/security/security_selinux.c --- libvirt.orig/src/security/security_selinux.c 2009-09-22 12:51:57.000000000 -0500 +++ libvirt/src/security/security_selinux.c 2009-09-25 16:56:32.000000000 -0500 @@ -377,6 +377,7 @@ err:
static int SELinuxRestoreSecurityImageLabel(virConnectPtr conn, + virDomainObjPtr vm ATTRIBUTE_UNUSED, virDomainDiskDefPtr disk) { /* Don't restore labels on readoly/shared disks, because @@ -581,7 +582,8 @@ SELinuxRestoreSecurityLabel(virConnectPt rc = -1; } for (i = 0 ; i < vm->def->ndisks ; i++) { - if (SELinuxRestoreSecurityImageLabel(conn, vm->def->disks[i]) < 0) + if (SELinuxRestoreSecurityImageLabel(conn, vm, + vm->def->disks[i]) < 0) rc = -1; } VIR_FREE(secdef->model);
ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On Fri, Sep 25, 2009 at 05:47:35PM -0500, Jamie Strandboge wrote:
On Fri, 25 Sep 2009, Jamie Strandboge wrote:
[PATCH 1] patch_1_reenable-nonfile-labels.patch (Updated based on prior feedback): When James Morris originally submitted his sVirt patches (as seen in libvirt 0.6.1), he did not require on disk labelling for virSecurityDomainRestoreImageLabel. A later commit[2] changed this behavior to assume on disk labelling, which halts implementations for path-based MAC systems such as AppArmor and TOMOYO where vm->def->seclabel is required to obtain the label. This patch simply adds the 'virDomainObjPtr vm' argument back to *RestoreImageLabel.
Okay, that one is trivial, pushed, thanks ! :-) Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

On Fri, 25 Sep 2009, Jamie Strandboge wrote:
[PATCH 2] patch_2_apparmor_driver.patch (updated and merged into one patch based on prior feedback): - Updates src/security.c for AppArmor - Adds security_apparmor.c, security_apparmor.h, virt-aa-helper.c and updates po/POTFILES.in. virt-aa-helper.c is a new binary which is used exclusively by the AppArmor security driver to manipulate AppArmor. - Adds tests for virt-aa-helper and the security driver. secaatest.c is identical to seclabeltest.c except it initializes the 'apparmor' driver instead of 'selinux'. These tests are integrated into 'make check' and pass. - Updates configure.in, Makefile.am, src/Makefile.am, tests/Makefile.am, and tools/Makefile.am for AppArmor. It is based on and should operate the same as the SELinux configuration.
-- Jamie Strandboge | http://www.canonical.com

On Fri, Sep 25, 2009 at 05:49:01PM -0500, Jamie Strandboge wrote:
diff -Naurp libvirt.orig/src/security/security_apparmor.c libvirt/src/security/security_apparmor.c --- libvirt.orig/src/security/security_apparmor.c 1969-12-31 18:00:00.000000000 -0600 +++ libvirt/src/security/security_apparmor.c 2009-09-25 17:00:13.000000000 -0500 +/* + * profile_status returns '-1' on error, '0' if loaded + * + * If check_enforcing is set to '1', then returns '-1' on error, '0' if + * loaded in complain mode, and '1' if loaded in enforcing mode. + */ +static int +profile_status(const char *str, const int check_enforcing) +{ + char *content = NULL; + char *tmp = NULL; + char *etmp = NULL; + int rc = -1; + + /* create string that is '<str> \0' for accurate matching */ + if (virAsprintf(&tmp, "%s ", str) == -1) + return rc; + + if (check_enforcing != 0) { + /* create string that is '<str> (enforce)\0' for accurate matching */ + if (virAsprintf(&etmp, "%s (enforce)", str) == -1) { + VIR_FREE(tmp); + return rc; + } + } + + if (virFileReadAll(APPARMOR_PROFILES_PATH, MAX_FILE_LEN, &content) < 0) { + virReportSystemError(NULL, errno, + _("Failed to read AppArmor profiles list " + "\'%s\'"), APPARMOR_PROFILES_PATH); + if (check_enforcing != 0) + VIR_FREE(etmp); + VIR_FREE(tmp); + return rc; + } + + if (strstr(content, tmp) != NULL) + rc = 0; + if (check_enforcing != 0) { + if (rc == 0 && strstr(content, etmp) != NULL) + rc = 1; /* return '1' if loaded and enforcing */ + VIR_FREE(etmp); + }
Since you've already got yourself a 'rc' variable declared & initialized, it'd benice to replace the duplicated error paths: VIR_FREE(tmp) return rc; With just a 'goto cleanup', and put a cleanup: label just at the endd here:
+ VIR_FREE(tmp); + VIR_FREE(content); + + return rc; +} +
+/* + * profile_status_file returns '-1' on error, '0' if file on disk is in + * complain mode and '1' if file on disk is in enforcing mode + */ +static int +profile_status_file(const char *str) +{ + char profile[PATH_MAX]; + char *content = NULL; + char *tmp = NULL; + int rc = -1; + int len; + + if (snprintf(profile, PATH_MAX, "%s/%s", APPARMOR_DIR "/libvirt", str) + > PATH_MAX - 1) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + "%s", _("profile name exceeds maximum length")); + } + + if (!virFileExists(profile)) { + return rc; + } + + if ((len = virFileReadAll(profile, MAX_FILE_LEN, &content)) < 0) { + virReportSystemError(NULL, errno, + _("Failed to read \'%s\'"), profile); + return rc; + } + + /* create string that is ' <str> flags=(complain)\0' */ + if (virAsprintf(&tmp, " %s flags=(complain)", str) == -1) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + "%s", _("could not allocate memory"));
This one should just be virReportOOMError(NULL);
+ VIR_FREE(content); + return rc; + } + + if (strstr(content, tmp) != NULL) + rc = 0; + else + rc = 1; + + VIR_FREE(tmp); + VIR_FREE(content); + + return rc;
Similar thought here about removing the duplicated cleanup paths in favour of just jumping to the end of the method & having a single cleanup code block
+static int +load_profile(virConnectPtr conn, const char *profile, virDomainObjPtr vm, + virDomainDiskDefPtr disk) +{ + const char *argv[7]; + int rc = -1, status; + bool create = true; + char *xml = NULL; + int pipefd[2]; + pid_t child; + + if (pipe(pipefd) < -1) { + virReportSystemError(conn, errno, "%s", _("unable to create pipe")); + return rc; + } + + xml = virDomainDefFormat(conn, vm->def, VIR_DOMAIN_XML_SECURE); + if (!xml) { + virSecurityReportError(conn, VIR_ERR_ERROR, + "%s", _("could not format XML")); + goto failed; + }
No need to report an error when the virDomainDef* functions fail, since they'll have already done that for you.
+ + if (profile_status_file(profile) >= 0) + create = false; + + argv[0] = VIRT_AA_HELPER_PATH; + argv[2] = (char *) "-u"; + argv[3] = profile; + argv[4] = NULL; + if (create) + argv[1] = (char *) "-c"; + else { + argv[1] = (char *) "-r"; + if (disk && disk->src) { + argv[4] = "-f"; + argv[5] = disk->src; + argv[6] = NULL; + } + }
+ if (virExec(conn, argv, NULL, NULL, &child, + pipefd[0], NULL, NULL, VIR_EXEC_CLEAR_CAPS) < 0) + goto clean;
Casting away const multiple times here. THis can be avoided by changing const char *argv[7]; to be const char * const argv[7]; I find the out-of-order initialization of argv indicies here a little confusing. Even though it'd be duplicating a little, I think it'd be clearer to have if (create) { const char *const argv[] = { VIR_AA_HELPER_PATH, "-c", "-u", profile, NULL }; ret = virExec(conn, argv, NULL, NULL, &child, pipefd[0], NULL, NULL, VIR_EXEC_CLEAR_CAPS); } else if (disk && disk->src) { const char *const argv[] = { VIR_AA_HELPER_PATH, "-r", "-u", profile, "-f", disk->src, NULL }; ret = virExec(conn, argv, NULL, NULL, &child, pipefd[0], NULL, NULL, VIR_EXEC_CLEAR_CAPS); } else { const char *const argv[] = { VIR_AA_HELPER_PATH, "-r", "-u", profile, NULL }; ret = virExec(conn, argv, NULL, NULL, &child, pipefd[0], NULL, NULL, VIR_EXEC_CLEAR_CAPS); } if (ret < 0) goto cleanup;
+ + + /* parent continues here */ + if (safewrite(pipefd[1], xml, strlen(xml)) < 0) { + virReportSystemError(conn, errno, "%s", _("unable to write to pipe")); + goto clean; + } + close(pipefd[1]); + rc = 0; + + rewait: + if (waitpid(child, &status, 0) != child) { + if (errno == EINTR) + goto rewait; + + virSecurityReportError(conn, VIR_ERR_ERROR, + _("Unexpected exit status from virt-aa-helper " + "%d pid %lu"), + WEXITSTATUS(status), (unsigned long)child); + rc = -1; + } + + clean: + VIR_FREE(xml); + + failed: + if (pipefd[0] > 0) + close(pipefd[0]); + if (pipefd[1] > 0) + close(pipefd[1]); + + return rc; +}
+ +static int +remove_profile(const char *profile) +{ + const char *argv[5]; + int rc = -1; + + argv[0] = VIRT_AA_HELPER_PATH; + argv[1] = (char *) "-R"; + argv[2] = (char *) "-u"; + argv[3] = profile; + argv[4] = NULL;
To preserve const-ness & remove hardcoded array indexes, this could be written as const char * const argv[] = { VIRT_AA_HELPER_PATH, "-R", "-u", profile, NULL };
+ + if (virRun(NULL, argv, NULL) == 0) + rc = 0; + + return rc; +} + +/* + * profile_name is buffer to hold name and len is how many bytes in the + * buffer + */ +static int +get_profile_name(virConnectPtr conn, virDomainObjPtr vm, + char *profile_name, const size_t len) +{ + virDomainPtr dom = NULL; + int rc = -1; + + if (len < PROFILE_NAME_SIZE) { + virSecurityReportError(conn, VIR_ERR_ERROR, + "%s", _("profile_name has wrong size")); + return rc; + } + profile_name[0] = '\0'; + strcat(profile_name, AA_PREFIX); + + /* generate the profile name */ + dom = virGetDomain(conn, vm->def->name, vm->def->uuid); + dom->id = vm->def->id; + + if (virDomainGetUUIDString(dom, + &profile_name[strlen(profile_name)]) != 0) { + virSecurityReportError(conn, VIR_ERR_ERROR, + "%s", _("could not find uuid for VM")); + return rc; + } + + return 0; +}
I'm not really following why this code needs to call out to the public API just to get the UUID into string format. It not clear that this is sufficiently checking the allocate char * is large enough for the UUID string result. I think I'd prefer to see this returning a string allocated on the heap, rather than the caller's stack. The whole method could thus be simplified down to just char *uuidstr[VIR_UUID_STRING_BUFLEN]; char *ret; virUUIDFormat(vm->def->uuid, uuidstr); if (virAsprintf(&ret, "%s%s", AA_PREFIX, uuidstr) < 0) return NULL; return ret;
+/* returns -1 on error or profile for libvirtd is unconfined, 0 if complain + * mode and 1 if enforcing + */ +static int +use_apparmor(void) +{ + char libvirt_daemon[PATH_MAX]; + int rc = -1; + ssize_t len = 0; + + if ((len = readlink("/proc/self/exe", libvirt_daemon, + PATH_MAX - 1)) < 0) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + "%s", _("could not find libvirtd")); + return rc; + } + libvirt_daemon[len] = '\0'; + + if (access(APPARMOR_PROFILES_PATH, R_OK) != 0) + return rc; + + return profile_status(libvirt_daemon, 1); +}
Is this the best way to check if app armour is enabled on a host ? It ought to be possible to use the app armour security driver for protecting guests regardless of whether the libvirtd daemon itself has a profile surely.
+static int +AppArmorGenSecurityLabel(virConnectPtr conn, virDomainObjPtr vm) +{ + int rc = -1; + char profile_name[PROFILE_NAME_SIZE]; + + profile_name[0] = '\0'; + + if ((vm->def->seclabel.label) || + (vm->def->seclabel.model) || (vm->def->seclabel.imagelabel)) { + virSecurityReportError(conn, VIR_ERR_ERROR, + "%s", + _("security label already defined for VM")); + return rc; + } + + if (get_profile_name(conn, vm, profile_name, sizeof(profile_name)) < 0) + return rc; + + /* if the profile is not already loaded, then load one */ + if (profile_loaded(profile_name) < 0) { + if (load_profile(conn, profile_name, vm, NULL) < 0) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _("cannot generate AppArmor profile " + "\'%s\'"), profile_name); + return rc; + } + } + + vm->def->seclabel.label = strndup(profile_name, strlen(profile_name)); + if (!vm->def->seclabel.label) { + virSecurityReportError(conn, VIR_ERR_ERROR, + "%s", _("cannot generate AppArmor profile " + "name (label)")); + goto err; + } + + /* set imagelabel the same as label (but we won't use it) */ + vm->def->seclabel.imagelabel = strndup(profile_name, + strlen(profile_name)); + if (!vm->def->seclabel.imagelabel) { + virSecurityReportError(conn, VIR_ERR_ERROR, + "%s", + _("cannot generate AppArmor profile name " + "(imagelabel)")); + goto err; + }
These two strndup failures should also be virReportOOMError()
+ + vm->def->seclabel.model = strdup(SECURITY_APPARMOR_NAME); + if (!vm->def->seclabel.model) { + virReportOOMError(conn); + goto err; + } + + rc = 0; + goto done; + + err: + remove_profile(profile_name); + VIR_FREE(vm->def->seclabel.label); + VIR_FREE(vm->def->seclabel.imagelabel); + VIR_FREE(vm->def->seclabel.model); + done: + return rc; +} +
diff -Naurp libvirt.orig/tools/Makefile.am libvirt/tools/Makefile.am --- libvirt.orig/tools/Makefile.am 2009-09-22 12:51:57.000000000 -0500 +++ libvirt/tools/Makefile.am 2009-09-25 17:00:13.000000000 -0500 @@ -51,6 +51,33 @@ virsh_CFLAGS = \ $(COVERAGE_CFLAGS) \ $(LIBXML_CFLAGS) \ $(READLINE_CFLAGS) + +if WITH_SECDRIVER_APPARMOR +bin_PROGRAMS += virt-aa-helper
Since this program is only really intended for libvirt to call rather than general admin usage I think it'd be beter to list it as libexec_PROGRAMS, so it ends up in /usr/libexec instead of /usr/bin I think I'd actually have it in the src/security/ directory, since we usually keep libexec programs alongside the driver code that's using them, just have tools/ for end user programs
+/* + * run an apparmor_parser command + */ +static int +parserCommand(const char *profile_name, const char cmd) +{ + const char *argv[4]; + char flag[3]; + char profile[PATH_MAX]; + + if (strchr("arR", cmd) == NULL) { + vah_error(NULL, 0, "invalid flag"); + return -1; + } + + snprintf(flag, 3, "-%c", cmd); + + if (snprintf(profile, PATH_MAX, "%s/%s", + APPARMOR_DIR "/libvirt", profile_name) > PATH_MAX - 1) { + vah_error(NULL, 0, "profile name exceeds maximum length"); + return -1; + } + + if (!virFileExists(profile)) { + vah_error(NULL, 0, "profile does not exist"); + return -1; + } + + argv[0] = (char *) "/sbin/apparmor_parser"; + argv[1] = flag; + argv[2] = profile; + argv[3] = NULL;
Same issue with const-ness as earlier examples
+ + if (virRun(NULL, argv, NULL) != 0) { + vah_error(NULL, 0, "failed to run apparmor_parser"); + return -1; + } + + return 0; +}
+static int +valid_uuid(const char *uuid) +{ + int i; + + if (strlen(uuid) != PROFILE_NAME_SIZE - 1) + return -1; + + if (STRNEQLEN(AA_PREFIX, uuid, strlen(AA_PREFIX))) + return -1;
This one can just be if (!STRPREFIX(uuid, AA_PREFIX)) return -1;
+ + for (i = strlen(AA_PREFIX); i < PROFILE_NAME_SIZE - 1; i++) { + if (uuid[i] == '-') + continue; + if (!c_isxdigit(uuid[i])) + return -1; + } + return 0; +}
How about just using virUUIDParse to check UUID validity, such as char rawuuid[VIR_UUID_BUFLEN]; if (virUUIDParse(uuid + strlen(AA_PREFIX), rawuuid) < 0) return -1; return 0;
+ +static int +valid_name(const char *name) +{ + /* just try to filter out any dangerous characters in the name that can be + * used to subvert the profile */ + char *bad = (char *) " /[]*";
Please declare it const, rather than casting away const.
+ int i; + + if (strlen(name) == 0 || strlen(name) > PATH_MAX - 1) + return -1; + + for (i = 0; i < strlen(bad); i++) + if (strchr(name, bad[i]) != NULL) + return -1;
FYI, there'a convenient libc function for checking a string for bad characters - strspn, you can use it in this way: if (strspn(name, bad) != strlen(name)) return -1;
+static int +valid_path(const char *path, const bool readonly) +{ + struct stat sb; + int i; + int npaths = 20; + char **restricted; + int rc = -1; + + if (path == NULL || strlen(path) > PATH_MAX - 1) { + vah_error(NULL, 0, "bad pathname"); + return rc; + } + + /* Don't allow double quotes, since we use them to quote the filename + * and this will confuse the apparmor parser. + */ + if (strchr(path, '"') != NULL) + return 1; + + if (VIR_ALLOC_N(restricted, npaths) < 0) { + vah_error(NULL, 0, "could not allocate memory for paths"); + return rc; + } + + restricted[0] = (char *) "/bin/"; + restricted[1] = (char *) "/etc/"; + restricted[2] = (char *) "/lib"; + restricted[3] = (char *) "/lost+found/"; + restricted[4] = (char *) "/proc/"; + restricted[5] = (char *) "/sbin/"; + restricted[6] = (char *) "/selinux/"; + restricted[7] = (char *) "/sys/"; + restricted[8] = (char *) "/usr/bin/"; + restricted[9] = (char *) "/usr/lib"; + restricted[10] = (char *) "/usr/sbin/"; + restricted[11] = (char *) "/usr/share/"; + restricted[12] = (char *) "/usr/local/bin/"; + restricted[13] = (char *) "/usr/local/etc/"; + restricted[14] = (char *) "/usr/local/lib"; + restricted[15] = (char *) "/usr/local/sbin/"; + /* these paths are ok for readonly */ + restricted[16] = (char *) "/boot/"; + restricted[17] = (char *) "/vmlinuz"; + restricted[18] = (char *) "/initrd"; + restricted[19] = (char *) "/initrd.img"; + + /* don't check paths ok for readonly */ + if (readonly) + npaths = 16;
More const issues. Rather than manually declaring the array size, and indexing manually its safer to initialize the array at time of declaration with all the strings. I'd probably go for separate arrays for the read-write vs read-only difference
+ + if (!virFileExists(path)) + vah_warning("path does not exist, skipping file type checks"); + else { + if (stat(path, &sb) == -1) + goto end; + + rc = 1; + switch (sb.st_mode & S_IFMT) { + case S_IFDIR: + goto end; + break; + case S_IFIFO: + goto end; + break; + case S_IFSOCK: + goto end; + break; + default: + break; + } + } + + for (i = 0; i < npaths; i++) { + if (strlen(path) < strlen(restricted[i])) + continue; + + if (STREQLEN(path, restricted[i], strlen(restricted[i]))) { + rc = 1; + goto end; + } + } + rc = 0; + + end: + VIR_FREE(restricted); + return rc; +}
+ + +static int +vah_add_file(virBufferPtr buf, const char *path, const char *perms) +{ + char *tmp = NULL; + int rc = -1; + bool readonly = true; + + if (path == NULL) + return rc; + + if (virFileExists(path)) { + if ((tmp = realpath(path, NULL)) == NULL) { + vah_error(NULL, 0, path); + vah_error(NULL, 0, " could not find realpath for disk"); + return rc; + } + } else + if ((tmp = strdup(path)) == NULL) + return rc; + + if (strchr(perms, 'w') != NULL) + readonly = false; + + rc = valid_path(tmp, readonly); + if (rc != 0) { + if (rc > 0) { + vah_error(NULL, 0, path); + vah_error(NULL, 0, " skipped restricted file"); + } + goto clean; + } + + virBufferVSprintf(buf, " \"%s\" %s,\n", tmp, perms); + + if (virBufferError(buf)) { + vah_error(NULL, 0, "failed to allocate file buffer"); + rc = -1; + }
It is not really neeccesssar to check virBufferError here. The idea is that you can call virBufferVSprintf() as many times as you like in a row without any checking, and then only check virBufferError at the end when you've finishing writing to the buffer.
+ + clean: + free(tmp); + + return rc; +} + +static int +get_files(vahControl * ctl) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + virBuffer uuid_buf = VIR_BUFFER_INITIALIZER; + int i; + int rc = -1; + char *uuid; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + /* verify uuid is same as what we were given on the command line */ + virUUIDFormat(ctl->def->uuid, uuidstr); + virBufferVSprintf(&uuid_buf, "%s%s", AA_PREFIX, uuidstr); + if (virBufferError(&uuid_buf)) { + vah_error(ctl, 0, "failed to allocate file buffer"); + return rc; + } + uuid = virBufferContentAndReset(&uuid_buf);
Using virBuffer for a single printf here is a little overkill - the virAsprintf() function would be more than sufficient
+ if (STRNEQ(uuid, ctl->uuid)) { + vah_error(ctl, 0, "given uuid does not match XML uuid"); + goto clean; + } + + for (i = 0; i < ctl->def->ndisks; i++) + if (ctl->def->disks[i] && ctl->def->disks[i]->src) + if (vah_add_file(&buf, ctl->def->disks[i]->src, "rw") != 0) + goto clean; + + for (i = 0; i < ctl->def->nserials; i++) + if (ctl->def->serials[i] && ctl->def->serials[i]->data.file.path) + if (vah_add_file(&buf, + ctl->def->serials[i]->data.file.path, "w") != 0) + goto clean; + + if (ctl->def->console && ctl->def->console->data.file.path) + if (vah_add_file(&buf, ctl->def->console->data.file.path, "w") != 0) + goto clean; + + if (ctl->def->os.kernel && ctl->def->os.kernel) + if (vah_add_file(&buf, ctl->def->os.kernel, "r") != 0) + goto clean; + + if (ctl->def->os.initrd && ctl->def->os.initrd) + if (vah_add_file(&buf, ctl->def->os.initrd, "r") != 0) + goto clean; + + if (ctl->def->os.loader && ctl->def->os.loader) + if (vah_add_file(&buf, ctl->def->os.loader, "r") != 0) + goto clean; + + /* needs patch to hostusb.h to work + for (i = 0; i < ctl->def->nhostdevs; i++) + if (ctl->def->hostdevs[i]) { + virDomainHostdevDefPtr hostdev = ctl->def->hostdevs[i]; + if (hostdev->source.subsys.type == + VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) { + usbDevice *usb = usbGetDevice(NULL, + hostdev->source.subsys.u.usb.bus, + hostdev->source.subsys.u.usb.device); + if (usb == NULL) + continue; + rc = vah_add_file(&buf, usb->path, "rw"); + usbFreeDevice(NULL, usb);
This is the bit of code that should be changed to use the usbDeviceFileIterate() method, since you can't assume there is only 1 path which is why we didn't make 'path' public.
+ if (rc != 0) + goto clean; + } + } + */ + + if (ctl->newdisk) + if (vah_add_file(&buf, ctl->newdisk, "rw") != 0) + goto clean;
If you remove the virBufferError check from the vah_add_file calls, it could just go in here
+ + rc = 0; + ctl->files = virBufferContentAndReset(&buf); + clean: + VIR_FREE(uuid); + return rc; +} +
Regards, Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

Attached is an updated patch_2_apparmor_driver_updated.patch and patch_3_docs_updated.patch. Thanks for the review! These two patches along with the previous patch_1_reenable-nonfile-labels.patch pass syntax-check, make check (for the tests I added) and introduce no regressions over the previous patch. See below for inline comments. In addition to implenting your suggestions, the patch also now supports <readonly/> for disks and I defensively quote the pid, monitor and logfile. I also added ReserveSecurityLabel (a noop) along with stubs for SetSecurityHostdevLabel() and RestoreSecurityHostdevLabel(). Jamie On Wed, 30 Sep 2009, Daniel P. Berrange wrote:
+static int +profile_status(const char *str, const int check_enforcing) +{
Since you've already got yourself a 'rc' variable declared & initialized, it'd benice to replace the duplicated error paths:
Done (along with others you mentioned).
+static int +profile_status_file(const char *str)
This one should just be virReportOOMError(NULL);
Done (along with others you mentioned).
+static int +load_profile(virConnectPtr conn, const char *profile, virDomainObjPtr vm,
No need to report an error when the virDomainDef* functions fail, since they'll have already done that for you.
Done
Casting away const multiple times here. THis can be avoided by changing
This is a favorite thing for me to do. :P Done (along with others you mentioned).
I find the out-of-order initialization of argv indicies here a little confusing. Even though it'd be duplicating a little, I think it'd be clearer to have
Done
I think I'd prefer to see this returning a string allocated on the heap, rather than the caller's stack. The whole method could thus be simplified down to just
Done
+/* returns -1 on error or profile for libvirtd is unconfined, 0 if complain + * mode and 1 if enforcing + */ +static int +use_apparmor(void)
Is this the best way to check if app armour is enabled on a host ? It ought to be possible to use the app armour security driver for protecting guests regardless of whether the libvirtd daemon itself has a profile surely.
Actually, this is required. Currently, an unconfined process may not aa_change_profile(), which is why I am very careful here.
+if WITH_SECDRIVER_APPARMOR +bin_PROGRAMS += virt-aa-helper
Since this program is only really intended for libvirt to call rather than general admin usage I think it'd be beter to list it as libexec_PROGRAMS, so it ends up in /usr/libexec instead of /usr/bin
I think I'd actually have it in the src/security/ directory, since we usually keep libexec programs alongside the driver code that's using them, just have tools/ for end user programs
Done and done.
+ if (STRNEQLEN(AA_PREFIX, uuid, strlen(AA_PREFIX))) + return -1;
This one can just be
if (!STRPREFIX(uuid, AA_PREFIX)) return -1;
Done
How about just using virUUIDParse to check UUID validity, such as
char rawuuid[VIR_UUID_BUFLEN]; if (virUUIDParse(uuid + strlen(AA_PREFIX), rawuuid) < 0) return -1; return 0;
Done
FYI, there'a convenient libc function for checking a string for bad characters - strspn, you can use it in this way:
if (strspn(name, bad) != strlen(name)) return -1;
Ah, missed that one. I actually used strcspn() instead. Done
+static int +valid_path(const char *path, const bool readonly)
More const issues. Rather than manually declaring the array size, and indexing manually its safer to initialize the array at time of declaration with all the strings. I'd probably go for separate arrays for the read-write vs read-only difference
Done
+static int +vah_add_file(virBufferPtr buf, const char *path, const char *perms) + + if (virBufferError(buf)) { + vah_error(NULL, 0, "failed to allocate file buffer"); + rc = -1; + }
It is not really neeccesssar to check virBufferError here. The idea is that you can call virBufferVSprintf() as many times as you like in a row without any checking, and then only check virBufferError at the end when you've finishing writing to the buffer.
Done
+static int +get_files(vahControl * ctl) +{ + virBuffer uuid_buf = VIR_BUFFER_INITIALIZER;
Using virBuffer for a single printf here is a little overkill - the virAsprintf() function would be more than sufficient
Yeah, at one point I was thinking of doing more here and forgot to go back to virAsprintf(). Done.
+ /* needs patch to hostusb.h to work + for (i = 0; i < ctl->def->nhostdevs; i++) + if (ctl->def->hostdevs[i]) { + virDomainHostdevDefPtr hostdev = ctl->def->hostdevs[i]; + if (hostdev->source.subsys.type == + VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) { + usbDevice *usb = usbGetDevice(NULL, + hostdev->source.subsys.u.usb.bus, + hostdev->source.subsys.u.usb.device); + if (usb == NULL) + continue; + rc = vah_add_file(&buf, usb->path, "rw"); + usbFreeDevice(NULL, usb);
This is the bit of code that should be changed to use the usbDeviceFileIterate() method, since you can't assume there is only 1 path which is why we didn't make 'path' public.
Done. virt-aa-helper now does this right, but overall attach-device with a USB or PCI host device still does not work (need to figure out the best way to get the hostdev XML to virt-aa-helper, as previously discussed). Users can adjust the profile/abstraction in the meantime as desired.
If you remove the virBufferError check from the vah_add_file calls, it could just go in here
Done -- Jamie Strandboge | http://www.canonical.com

On Mon, Oct 05, 2009 at 04:00:02PM -0500, Jamie Strandboge wrote:
Attached is an updated patch_2_apparmor_driver_updated.patch and patch_3_docs_updated.patch. Thanks for the review! These two patches along with the previous patch_1_reenable-nonfile-labels.patch pass syntax-check, make check (for the tests I added) and introduce no regressions over the previous patch. See below for inline comments. In addition to implenting your suggestions, the patch also now supports <readonly/> for disks and I defensively quote the pid, monitor and logfile. I also added ReserveSecurityLabel (a noop) along with stubs for SetSecurityHostdevLabel() and RestoreSecurityHostdevLabel(). [...] +static int +profile_status(const char *str, const int check_enforcing) +{ + char *content = NULL; + char *tmp = NULL; + char *etmp = NULL; + int rc = -1; + + /* create string that is '<str> \0' for accurate matching */ + if (virAsprintf(&tmp, "%s ", str) == -1) + return rc; + + if (check_enforcing != 0) { + /* create string that is '<str> (enforce)\0' for accurate matching */ + if (virAsprintf(&etmp, "%s (enforce)", str) == -1) { + VIR_FREE(tmp); + return rc; + } + } + + if (virFileReadAll(APPARMOR_PROFILES_PATH, MAX_FILE_LEN, &content) < 0) { + virReportSystemError(NULL, errno, + _("Failed to read AppArmor profiles list " + "\'%s\'"), APPARMOR_PROFILES_PATH);
----
+ if (check_enforcing != 0) + VIR_FREE(etmp); + goto clean;
+ } + + if (strstr(content, tmp) != NULL) + rc = 0; + if (check_enforcing != 0) { + if (rc == 0 && strstr(content, etmp) != NULL) + rc = 1; /* return '1' if loaded and enforcing */
+ VIR_FREE(etmp);
I would remove those 2 blocks and just VIR_FREE(etmp) in clean: since etmp is initialized to NULL, it's simpler.
+ } + + VIR_FREE(content); + clean: + VIR_FREE(tmp); + + return rc; +} + [...] +static int +profile_status_file(const char *str) +{ + char profile[PATH_MAX]; + char *content = NULL; + char *tmp = NULL; + int rc = -1; + int len; + + if (snprintf(profile, PATH_MAX, "%s/%s", APPARMOR_DIR "/libvirt", str) + > PATH_MAX - 1) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + "%s", _("profile name exceeds maximum length")); + }
rather than allocating a full page on the stack for profile, virAsprintf is probably simpler and more efficient but would need an unified exit to free it up.
+ if (!virFileExists(profile)) { + return rc; + } + + if ((len = virFileReadAll(profile, MAX_FILE_LEN, &content)) < 0) { + virReportSystemError(NULL, errno, + _("Failed to read \'%s\'"), profile); + return rc; + } + + /* create string that is ' <str> flags=(complain)\0' */ + if (virAsprintf(&tmp, " %s flags=(complain)", str) == -1) { + virReportOOMError(NULL); + goto clean; + } + + if (strstr(content, tmp) != NULL) + rc = 0; + else + rc = 1; + + VIR_FREE(tmp); + clean: + VIR_FREE(content); + + return rc; +} [...] +static int +load_profile(virConnectPtr conn, const char *profile, virDomainObjPtr vm, + virDomainDiskDefPtr disk) +{ + int rc = -1, status, ret; + bool create = true; + char *xml = NULL; + int pipefd[2]; + pid_t child; + + if (pipe(pipefd) < -1) { + virReportSystemError(conn, errno, "%s", _("unable to create pipe")); + return rc; + } + + xml = virDomainDefFormat(conn, vm->def, VIR_DOMAIN_XML_SECURE); + if (!xml) + goto failed; + [...] + + clean: + VIR_FREE(xml); + + failed:
no need for a special failed: target, VIR_FREE(NULL) is fine. [...]
+static int +use_apparmor(void) +{ + char libvirt_daemon[PATH_MAX]; + int rc = -1; + ssize_t len = 0; + + if ((len = readlink("/proc/self/exe", libvirt_daemon, + PATH_MAX - 1)) < 0) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + "%s", _("could not find libvirtd")); + return rc; + } + libvirt_daemon[len] = '\0';
Hum, readlink() can be a bit nasty, I would suggest to use virFileResolveLink() here.
+ if (access(APPARMOR_PROFILES_PATH, R_OK) != 0) + return rc; + + return profile_status(libvirt_daemon, 1); +} + +/* Called on libvirtd startup to see if AppArmor is available */ +static int +AppArmorSecurityDriverProbe(void) +{ + char template[PATH_MAX]; + + if (use_apparmor() < 0) + return SECURITY_DRIVER_DISABLE; + + /* see if template file exists */ + if (snprintf(template, PATH_MAX, "%s/TEMPLATE", + APPARMOR_DIR "/libvirt") > PATH_MAX - 1) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + "%s", _("template too large")); + return SECURITY_DRIVER_DISABLE; + }
again I would suggest to not allocate MAX_PATH on the stack, but using virAsprintf, but this would mean changing the function exit code.
+ if (!virFileExists(template)) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + _("template \'%s\' does not exist"), template); + return SECURITY_DRIVER_DISABLE; + } + + return SECURITY_DRIVER_ENABLE; +} [...]
ACK for me on the changes to the library. I spotted a few possible improvements but this can be done in a later patch, no need to review everything again IMHO.
diff -Naurp libvirt.orig/src/security/virt-aa-helper.c libvirt/src/security/virt-aa-helper.c --- libvirt.orig/src/security/virt-aa-helper.c 1969-12-31 18:00:00.000000000 -0600 +++ libvirt/src/security/virt-aa-helper.c 2009-10-05 15:22:59.000000000 -0500
For virt-aa-helper, some of the changes could be made there too like not allocate 4k on the stack and use virAsprintf or unify some of the exit labels, but since it's standalone it's not a big deal. ACK for tests
diff -Naurp libvirt.orig/docs/drvqemu.html.in libvirt/docs/drvqemu.html.in --- libvirt.orig/docs/drvqemu.html.in 2009-10-05 10:32:51.000000000 -0500 +++ libvirt/docs/drvqemu.html.in 2009-10-05 10:32:07.000000000 -0500 @@ -296,6 +296,73 @@
and ACK for doc and examples. Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

On Mon, Oct 05, 2009 at 04:00:02PM -0500, Jamie Strandboge wrote:
Attached is an updated patch_2_apparmor_driver_updated.patch and patch_3_docs_updated.patch. Thanks for the review! These two patches along with the previous patch_1_reenable-nonfile-labels.patch pass syntax-check, make check (for the tests I added) and introduce no regressions over the previous patch. See below for inline comments. In addition to implenting your suggestions, the patch also now supports <readonly/> for disks and I defensively quote the pid, monitor and logfile. I also added ReserveSecurityLabel (a noop) along with stubs for SetSecurityHostdevLabel() and RestoreSecurityHostdevLabel().
Jamie
On Wed, 30 Sep 2009, Daniel P. Berrange wrote:
+static int +profile_status(const char *str, const int check_enforcing) +{
Since you've already got yourself a 'rc' variable declared & initialized, it'd benice to replace the duplicated error paths:
Done (along with others you mentioned).
+static int +profile_status_file(const char *str)
This one should just be virReportOOMError(NULL);
Done (along with others you mentioned).
+static int +load_profile(virConnectPtr conn, const char *profile, virDomainObjPtr vm,
No need to report an error when the virDomainDef* functions fail, since they'll have already done that for you.
Done
Casting away const multiple times here. THis can be avoided by changing
This is a favorite thing for me to do. :P Done (along with others you mentioned).
I find the out-of-order initialization of argv indicies here a little confusing. Even though it'd be duplicating a little, I think it'd be clearer to have
Done
I think I'd prefer to see this returning a string allocated on the heap, rather than the caller's stack. The whole method could thus be simplified down to just
Done
+/* returns -1 on error or profile for libvirtd is unconfined, 0 if complain + * mode and 1 if enforcing + */ +static int +use_apparmor(void)
Is this the best way to check if app armour is enabled on a host ? It ought to be possible to use the app armour security driver for protecting guests regardless of whether the libvirtd daemon itself has a profile surely.
Actually, this is required. Currently, an unconfined process may not aa_change_profile(), which is why I am very careful here.
+if WITH_SECDRIVER_APPARMOR +bin_PROGRAMS += virt-aa-helper
Since this program is only really intended for libvirt to call rather than general admin usage I think it'd be beter to list it as libexec_PROGRAMS, so it ends up in /usr/libexec instead of /usr/bin
I think I'd actually have it in the src/security/ directory, since we usually keep libexec programs alongside the driver code that's using them, just have tools/ for end user programs
Done and done.
+ if (STRNEQLEN(AA_PREFIX, uuid, strlen(AA_PREFIX))) + return -1;
This one can just be
if (!STRPREFIX(uuid, AA_PREFIX)) return -1;
Done
How about just using virUUIDParse to check UUID validity, such as
char rawuuid[VIR_UUID_BUFLEN]; if (virUUIDParse(uuid + strlen(AA_PREFIX), rawuuid) < 0) return -1; return 0;
Done
FYI, there'a convenient libc function for checking a string for bad characters - strspn, you can use it in this way:
if (strspn(name, bad) != strlen(name)) return -1;
Ah, missed that one. I actually used strcspn() instead. Done
+static int +valid_path(const char *path, const bool readonly)
More const issues. Rather than manually declaring the array size, and indexing manually its safer to initialize the array at time of declaration with all the strings. I'd probably go for separate arrays for the read-write vs read-only difference
Done
+static int +vah_add_file(virBufferPtr buf, const char *path, const char *perms) + + if (virBufferError(buf)) { + vah_error(NULL, 0, "failed to allocate file buffer"); + rc = -1; + }
It is not really neeccesssar to check virBufferError here. The idea is that you can call virBufferVSprintf() as many times as you like in a row without any checking, and then only check virBufferError at the end when you've finishing writing to the buffer.
Done
+static int +get_files(vahControl * ctl) +{ + virBuffer uuid_buf = VIR_BUFFER_INITIALIZER;
Using virBuffer for a single printf here is a little overkill - the virAsprintf() function would be more than sufficient
Yeah, at one point I was thinking of doing more here and forgot to go back to virAsprintf(). Done.
+ /* needs patch to hostusb.h to work + for (i = 0; i < ctl->def->nhostdevs; i++) + if (ctl->def->hostdevs[i]) { + virDomainHostdevDefPtr hostdev = ctl->def->hostdevs[i]; + if (hostdev->source.subsys.type == + VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) { + usbDevice *usb = usbGetDevice(NULL, + hostdev->source.subsys.u.usb.bus, + hostdev->source.subsys.u.usb.device); + if (usb == NULL) + continue; + rc = vah_add_file(&buf, usb->path, "rw"); + usbFreeDevice(NULL, usb);
This is the bit of code that should be changed to use the usbDeviceFileIterate() method, since you can't assume there is only 1 path which is why we didn't make 'path' public.
Done. virt-aa-helper now does this right, but overall attach-device with a USB or PCI host device still does not work (need to figure out the best way to get the hostdev XML to virt-aa-helper, as previously discussed). Users can adjust the profile/abstraction in the meantime as desired.
If you remove the virBufferError check from the vah_add_file calls, it could just go in here
Done
THanks for addressing all those issues. ACK to this new patch Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On Mon, Oct 05, 2009 at 04:00:02PM -0500, Jamie Strandboge wrote:
Attached is an updated patch_2_apparmor_driver_updated.patch and patch_3_docs_updated.patch. Thanks for the review! These two patches along with the previous patch_1_reenable-nonfile-labels.patch pass syntax-check, make check (for the tests I added) and introduce no regressions over the previous patch. See below for inline comments. In addition to implenting your suggestions, the patch also now supports <readonly/> for disks and I defensively quote the pid, monitor and logfile. I also added ReserveSecurityLabel (a noop) along with stubs for SetSecurityHostdevLabel() and RestoreSecurityHostdevLabel().
Okay, both patches are now commited, thanks ! There is just the few cleanups I suggested earlier but this can be done when you have time :-) Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

On Fri, 25 Sep 2009, Jamie Strandboge wrote:
[PATCH 3] patch_3_docs.patch (Updated based on prior feedback): Updates docs/drvqemu.html.in for AppArmor and adds profile examples to examples/apparmor. Updated based on prior feedback.
-- Jamie Strandboge | http://www.canonical.com

On Fri, Sep 25, 2009 at 05:44:35PM -0500, Jamie Strandboge wrote:
This simplifies adding new files. Part of this process involved adding support for kernel, initrd, loader, serial, and console. I have commented out code for hostdev (and pci would be easy enough), but it requires a patch to to move the _usbDevice struct fully into hostusb.h. I figure once the AppArmor driver is in, I can submit a patch for that.
No, the usbDevice struct is intended to be private because we want the ability to easily change it without impacting callers. The idea is that there is not neccessarily just 1 single path associated with a USB device and thus 'path' should not be exposed to callers. Instead if you need process the 1-or-more paths associated with a USB device you should invoke the usbDeviceFileIterate() method and the callback you supply will be executing once for each path. The same pattern is there for PCI devices which have between 1 and 4 paths that need dealing with typically.
This iteration also properly works with attach/detach. While I use virDomainDefFormat() primarily, this does not work with attach because the definition is not updated with the information yet (neither def nor newDef), so I pass the disk file on the command line. For now this is ok since SetSecurityImageLabel currently only deals with a single file, but this will need to be adjusted depending on how the storage backend patch works out. I thought about creating a new def and inserting the disk into it and sending it off to virt-aa-helper, but I couldn't find an easy way to get an accurate virCapsPtr in security_apparmor.c. I had a similar problem with virt-aa-helper, but there I just created a fake virCapsPtr (which defaults to 'hvm', which was a compromise I was willing to make at this time since sVirt only supports qemu right now). The issues with virCapsPtr aside, the code is overall cleaner and much more easily extendable, so thanks for the excellent feedback. :)
For virCapsPtr issues we should probably evaluate whether we can make the capabilities object passed to virDomainDefParse() optionally NULLable. The capabilities object is used to fill in missing pieces of metadata when getting an XML blob in from the client app. By the time we get into your virt-aa-helper, all this metadata ought to be present & correct, so in theory there should be no need for a virCapsPtr object. Does the virt-aa-helper need the full domain XML when attaching or detaching a disk ? If not, then you could try an approach where you pass the full domain XML when initially starting the guest, and then only pass the domain UUID + the device XML when attaching or detaching a particular device on the fly. Regards, Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On Tue, 29 Sep 2009, Daniel P. Berrange wrote:
On Fri, Sep 25, 2009 at 05:44:35PM -0500, Jamie Strandboge wrote:
This simplifies adding new files. Part of this process involved adding support for kernel, initrd, loader, serial, and console. I have commented out code for hostdev (and pci would be easy enough), but it requires a patch to to move the _usbDevice struct fully into hostusb.h. I figure once the AppArmor driver is in, I can submit a patch for that.
No, the usbDevice struct is intended to be private because we want the ability to easily change it without impacting callers. The idea is that there is not neccessarily just 1 single path associated with a USB device and thus 'path' should not be exposed to callers. Instead if you need process the 1-or-more paths associated with a USB device you should invoke the usbDeviceFileIterate() method and the callback you supply will be executing once for each path. The same pattern is there for PCI devices which have between 1 and 4 paths that need dealing with typically.
Ok, I'll look at this once the patch lands.
For virCapsPtr issues we should probably evaluate whether we can make the capabilities object passed to virDomainDefParse() optionally NULLable. The capabilities object is used to fill in missing pieces of metadata when getting an XML blob in from the client app. By the time we get into your virt-aa-helper, all this metadata ought to be present & correct, so in theory there should be no need for a virCapsPtr object.
I was hoping I could pass NULL, but as you said, as it's written, the caps is needed to fill in a lot of blanks. I didn't want to try to change that myself because I didn't have the overall picture and couldn't forsee all the side-effects, especially (but certainly not limited to ;) non-qemu domains.
Does the virt-aa-helper need the full domain XML when attaching or detaching a disk ? If not, then you could try an approach where you pass the full domain XML when initially starting the guest, and then only pass the domain UUID + the device XML when attaching or detaching a particular device on the fly.
virt-aa-helper will be called on startup via AppArmorGenSecurityLabel and then also when things change via AppArmorRestoreSecurityImageLabel and AppArmorSetSecurityImageLabel. For just attaching and detaching a disk, I could probably do as you suggest (I had already considered this), but the required virDomainDiskDefParseXML() is internal and thus not available to virt-aa-helper. I then thought it would probably be safest anyway to pass the complete XML since: a) the helper can always be sure it is in sync b) it makes it easier down the line if def or newDef is updated for some action. All I'd need to do is put a load_profile() call in the right place in security_apparmor.c rather than having to rewrite virt-aa-helper again to deal with a different XML situation. I also wasn't sure if it was a design decision that the def or newDef wasn't updated when attaching/detaching. I really wanted to just insert the disk definition using virDomainDiskInsert, which would have solved this issue completely, but I couldn't find a clean way to obtain a copy of a domain definition that I could modify, send off to virt-aa-helper, then discard. Thanks for your feedback! :) Jamie -- Jamie Strandboge | http://www.canonical.com
participants (3)
-
Daniel P. Berrange
-
Daniel Veillard
-
Jamie Strandboge