1 /*
2 * virt-aa-helper: wrapper program used by AppArmor security driver.
3 *
4 * Copyright (C) 2010-2014 Red Hat, Inc.
5 * Copyright (C) 2009-2011 Canonical Ltd.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see
19 * <http://www.gnu.org/licenses/>.
20 */
21
22 #include <config.h>
23
24 #include <stdarg.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27 #include <fcntl.h>
28 #include <getopt.h>
29 #include <sys/utsname.h>
30
31 #include "internal.h"
32 #include "virbuffer.h"
33 #include "viralloc.h"
34 #include "vircommand.h"
35 #include "virlog.h"
36 #include "driver.h"
37
38 #include "security_driver.h"
39 #include "security_apparmor.h"
40 #include "storage_source.h"
41 #include "domain_conf.h"
42 #include "virxml.h"
43 #include "viruuid.h"
44 #include "virusb.h"
45 #include "virutil.h"
46 #include "virpci.h"
47 #include "virfile.h"
48 #include "configmake.h"
49 #include "virrandom.h"
50 #include "virstring.h"
51 #include "virgettext.h"
52 #include "virhostdev.h"
53
54 #define VIR_FROM_THIS VIR_FROM_SECURITY
55
56 static char *progname;
57
58 typedef struct {
59 char uuid[PROFILE_NAME_SIZE]; /* UUID of vm */
60 bool dryrun; /* dry run */
61 char cmd; /* 'c' create
62 * 'a' add (load)
63 * 'r' replace
64 * 'R' remove */
65 char *files; /* list of files */
66 virDomainDef *def; /* VM definition */
67 virCaps *caps; /* VM capabilities */
68 virDomainXMLOption *xmlopt; /* XML parser data */
69 char *virtType; /* type of hypervisor (eg qemu, xen, lxc) */
70 char *os; /* type of os (eg hvm, xen, exe) */
71 virArch arch; /* machine architecture */
72 char *newfile; /* newly added file */
73 bool append; /* append to .files instead of rewrite */
74 } vahControl;
75
76 static int
vahDeinit(vahControl * ctl)77 vahDeinit(vahControl * ctl)
78 {
79 if (ctl == NULL)
80 return -1;
81
82 virDomainDefFree(ctl->def);
83 virObjectUnref(ctl->caps);
84 virObjectUnref(ctl->xmlopt);
85 VIR_FREE(ctl->files);
86 VIR_FREE(ctl->virtType);
87 VIR_FREE(ctl->os);
88 VIR_FREE(ctl->newfile);
89
90 return 0;
91 }
92
93 /*
94 * Print usage
95 */
96 static void
vah_usage(void)97 vah_usage(void)
98 {
99 printf(_("\n%s mode [options] [extra file] [< def.xml]\n\n"
100 " Modes:\n"
101 " -a | --add load profile\n"
102 " -c | --create create profile from template\n"
103 " -D | --delete unload profile and delete generated rules\n"
104 " -r | --replace reload profile\n"
105 " -R | --remove unload profile\n"
106 " Options:\n"
107 " -d | --dryrun dry run\n"
108 " -u | --uuid <uuid> uuid (profile name)\n"
109 " -h | --help this help\n"
110 " Extra File:\n"
111 " -f | --add-file <file> add file to a profile generated from XML\n"
112 " -F | --append-file <file> append file to an existing profile\n"
113 "\n"), progname);
114
115 puts(_("This command is intended to be used by libvirtd "
116 "and not used directly.\n"));
117 return;
118 }
119
120 static void
vah_error(vahControl * ctl,int doexit,const char * str)121 vah_error(vahControl * ctl, int doexit, const char *str)
122 {
123 fprintf(stderr, _("%s: error: %s%c"), progname, str, '\n');
124
125 if (doexit) {
126 if (ctl != NULL)
127 vahDeinit(ctl);
128 exit(EXIT_FAILURE);
129 }
130 }
131
132 static void
vah_warning(const char * str)133 vah_warning(const char *str)
134 {
135 fprintf(stderr, _("%s: warning: %s%c"), progname, str, '\n');
136 }
137
138 static void
vah_info(const char * str)139 vah_info(const char *str)
140 {
141 fprintf(stderr, _("%s:\n%s%c"), progname, str, '\n');
142 }
143
144 /*
145 * run an apparmor_parser command
146 */
147 static int
parserCommand(const char * profile_name,const char cmd)148 parserCommand(const char *profile_name, const char cmd)
149 {
150 int result = -1;
151 char flag[3];
152 char *profile;
153 int status;
154 int ret;
155
156 if (strchr("arR", cmd) == NULL) {
157 vah_error(NULL, 0, _("invalid flag"));
158 return -1;
159 }
160
161 g_snprintf(flag, 3, "-%c", cmd);
162
163 profile = g_strdup_printf("%s/%s", APPARMOR_DIR "/libvirt", profile_name);
164
165 if (!virFileExists(profile)) {
166 vah_error(NULL, 0, _("profile does not exist"));
167 goto cleanup;
168 } else {
169 const char * const argv[] = {
170 "/sbin/apparmor_parser", flag, profile, NULL
171 };
172 g_autoptr(virCommand) command = virCommandNewArgs(argv);
173
174 virCommandRawStatus(command);
175 if ((ret = virCommandRun(command, &status)) != 0 ||
176 (WIFEXITED(status) && WEXITSTATUS(status) != 0)) {
177 if (ret != 0) {
178 vah_error(NULL, 0, _("failed to run apparmor_parser"));
179 goto cleanup;
180 } else if (cmd == 'R' && WIFEXITED(status) &&
181 WEXITSTATUS(status) == 234) {
182 vah_warning(_("unable to unload already unloaded profile"));
183 } else {
184 vah_error(NULL, 0, _("apparmor_parser exited with error"));
185 goto cleanup;
186 }
187 }
188 }
189
190 result = 0;
191
192 cleanup:
193 VIR_FREE(profile);
194
195 return result;
196 }
197
198 /*
199 * Update the dynamic files
200 */
201 static int
update_include_file(const char * include_file,const char * included_files,bool append)202 update_include_file(const char *include_file, const char *included_files,
203 bool append)
204 {
205 int rc = -1;
206 int plen, flen = 0;
207 int fd;
208 char *pcontent = NULL;
209 char *existing = NULL;
210 const char *warning =
211 "# DO NOT EDIT THIS FILE DIRECTLY. IT IS MANAGED BY LIBVIRT.\n";
212
213 if (virFileExists(include_file)) {
214 flen = virFileReadAll(include_file, MAX_FILE_LEN, &existing);
215 if (flen < 0)
216 return rc;
217 }
218
219 if (append && virFileExists(include_file))
220 pcontent = g_strdup_printf("%s%s", existing, included_files);
221 else
222 pcontent = g_strdup_printf("%s%s", warning, included_files);
223
224 plen = strlen(pcontent);
225 if (plen > MAX_FILE_LEN) {
226 vah_error(NULL, 0, _("invalid length for new profile"));
227 goto cleanup;
228 }
229
230 /* only update the disk profile if it is different */
231 if (flen > 0 && flen == plen && STREQLEN(existing, pcontent, plen)) {
232 rc = 0;
233 goto cleanup;
234 }
235
236 /* write the file */
237 if ((fd = open(include_file, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1) {
238 vah_error(NULL, 0, _("failed to create include file"));
239 goto cleanup;
240 }
241
242 if (safewrite(fd, pcontent, plen) < 0) { /* don't write the '\0' */
243 VIR_FORCE_CLOSE(fd);
244 vah_error(NULL, 0, _("failed to write to profile"));
245 goto cleanup;
246 }
247
248 if (VIR_CLOSE(fd) != 0) {
249 vah_error(NULL, 0, _("failed to close or write to profile"));
250 goto cleanup;
251 }
252 rc = 0;
253
254 cleanup:
255 VIR_FREE(pcontent);
256 VIR_FREE(existing);
257
258 return rc;
259 }
260
261 /*
262 * Create a profile based on a template
263 */
264 static int
create_profile(const char * profile,const char * profile_name,const char * profile_files,int virtType)265 create_profile(const char *profile, const char *profile_name,
266 const char *profile_files, int virtType)
267 {
268 g_autofree char *template = NULL;
269 g_autofree char *tcontent = NULL;
270 g_autofree char *pcontent = NULL;
271 g_autofree char *replace_name = NULL;
272 g_autofree char *replace_files = NULL;
273 char *tmp = NULL;
274 const char *template_name = "\nprofile LIBVIRT_TEMPLATE";
275 const char *template_end = "\n}";
276 int tlen, plen;
277 int fd;
278 const char *driver_name = NULL;
279
280 if (virFileExists(profile)) {
281 vah_error(NULL, 0, _("profile exists"));
282 return -1;
283 }
284
285 switch (virtType) {
286 case VIR_DOMAIN_VIRT_QEMU:
287 case VIR_DOMAIN_VIRT_KQEMU:
288 case VIR_DOMAIN_VIRT_KVM:
289 driver_name = "qemu";
290 break;
291 default:
292 driver_name = virDomainVirtTypeToString(virtType);
293 }
294
295 template = g_strdup_printf("%s/TEMPLATE.%s", APPARMOR_DIR "/libvirt", driver_name);
296
297 if (!virFileExists(template)) {
298 vah_error(NULL, 0, _("template does not exist"));
299 return -1;
300 }
301
302 if ((tlen = virFileReadAll(template, MAX_FILE_LEN, &tcontent)) < 0) {
303 vah_error(NULL, 0, _("failed to read AppArmor template"));
304 return -1;
305 }
306
307 if (strstr(tcontent, template_name) == NULL) {
308 vah_error(NULL, 0, _("no replacement string in template"));
309 return -1;
310 }
311
312 if (strstr(tcontent, template_end) == NULL) {
313 vah_error(NULL, 0, _("no replacement string in template"));
314 return -1;
315 }
316
317 /* '\nprofile <profile_name>\0' */
318 replace_name = g_strdup_printf("\nprofile %s", profile_name);
319
320 /* '\n<profile_files>\n}\0' */
321 if (virtType != VIR_DOMAIN_VIRT_LXC)
322 replace_files = g_strdup_printf("\n%s\n}", profile_files);
323
324 plen = tlen + strlen(replace_name) - strlen(template_name) + 1;
325
326 if (virtType != VIR_DOMAIN_VIRT_LXC)
327 plen += strlen(replace_files) - strlen(template_end);
328
329 if (plen > MAX_FILE_LEN || plen < tlen) {
330 vah_error(NULL, 0, _("invalid length for new profile"));
331 return -1;
332 }
333
334 if (!(pcontent = virStringReplace(tcontent, template_name, replace_name)))
335 return -1;
336
337 if (virtType != VIR_DOMAIN_VIRT_LXC) {
338 if (!(tmp = virStringReplace(pcontent, template_end, replace_files)))
339 return -1;
340 VIR_FREE(pcontent);
341 pcontent = g_steal_pointer(&tmp);
342 }
343
344 /* write the file */
345 if ((fd = open(profile, O_CREAT | O_EXCL | O_WRONLY, 0644)) == -1) {
346 vah_error(NULL, 0, _("failed to create profile"));
347 return -1;
348 }
349
350 if (safewrite(fd, pcontent, plen - 1) < 0) { /* don't write the '\0' */
351 VIR_FORCE_CLOSE(fd);
352 vah_error(NULL, 0, _("failed to write to profile"));
353 return -1;
354 }
355
356 if (VIR_CLOSE(fd) != 0) {
357 vah_error(NULL, 0, _("failed to close or write to profile"));
358 return -1;
359 }
360
361 return 0;
362 }
363
364 /*
365 * Load an existing profile
366 */
367 static int
parserLoad(const char * profile_name)368 parserLoad(const char *profile_name)
369 {
370 return parserCommand(profile_name, 'a');
371 }
372
373 /*
374 * Remove an existing profile
375 */
376 static int
parserRemove(const char * profile_name)377 parserRemove(const char *profile_name)
378 {
379 return parserCommand(profile_name, 'R');
380 }
381
382 /*
383 * Replace an existing profile
384 */
385 static int
parserReplace(const char * profile_name)386 parserReplace(const char *profile_name)
387 {
388 return parserCommand(profile_name, 'r');
389 }
390
391 static int
valid_uuid(const char * uuid)392 valid_uuid(const char *uuid)
393 {
394 unsigned char rawuuid[VIR_UUID_BUFLEN];
395
396 if (strlen(uuid) != PROFILE_NAME_SIZE - 1)
397 return -1;
398
399 if (!STRPREFIX(uuid, AA_PREFIX))
400 return -1;
401
402 if (virUUIDParse(uuid + strlen(AA_PREFIX), rawuuid) < 0)
403 return -1;
404
405 return 0;
406 }
407
408 static int
valid_name(const char * name)409 valid_name(const char *name)
410 {
411 /* just try to filter out any dangerous characters in the name that can be
412 * used to subvert the profile */
413 const char *bad = "/[]{}?^,\"*";
414
415 if (strlen(name) == 0)
416 return -1;
417
418 if (strcspn(name, bad) != strlen(name))
419 return -1;
420
421 return 0;
422 }
423
424 /* see if one of the strings in arr starts with str */
425 static int
array_starts_with(const char * str,const char * const * arr,const long size)426 array_starts_with(const char *str, const char * const *arr, const long size)
427 {
428 size_t i;
429 for (i = 0; i < size; i++) {
430 if (strlen(str) < strlen(arr[i]))
431 continue;
432
433 if (STRPREFIX(str, arr[i]))
434 return 0;
435 }
436 return 1;
437 }
438
439 /*
440 * Don't allow access to special files or restricted paths such as /bin, /sbin,
441 * /usr/bin, /usr/sbin and /etc. This is in an effort to prevent read/write
442 * access to system files which could be used to elevate privileges. This is a
443 * safety measure in case libvirtd is under a restrictive profile and is
444 * subverted and trying to escape confinement.
445 *
446 * Note that we cannot exclude block devices because they are valid devices.
447 * The TEMPLATE file can be adjusted to explicitly disallow these if needed.
448 *
449 * RETURN: -1 on error, 0 if ok, 1 if blocked
450 */
451 static int
valid_path(const char * path,const bool readonly)452 valid_path(const char *path, const bool readonly)
453 {
454 const char * const restricted[] = {
455 "/bin/",
456 "/etc/",
457 "/lib",
458 "/lost+found/",
459 "/proc/",
460 "/sbin/",
461 "/selinux/",
462 "/sys/",
463 "/usr/bin/",
464 "/usr/lib",
465 "/usr/sbin/",
466 "/usr/share/",
467 "/usr/local/bin/",
468 "/usr/local/etc/",
469 "/usr/local/lib",
470 "/usr/local/sbin/"
471 };
472 /* these paths are ok for readonly, but not read/write */
473 const char * const restricted_rw[] = {
474 "/boot/",
475 "/vmlinuz",
476 "/initrd",
477 "/initrd.img",
478 "/usr/share/edk2/",
479 "/usr/share/OVMF/", /* for OVMF images */
480 "/usr/share/ovmf/", /* for OVMF images */
481 "/usr/share/AAVMF/", /* for AAVMF images */
482 "/usr/share/qemu-efi/", /* for AAVMF images */
483 "/usr/share/qemu-efi-aarch64/" /* for AAVMF images */
484 };
485 /* override the above with these */
486 const char * const override[] = {
487 "/sys/devices/pci", /* for hostdev pci devices */
488 "/sys/kernel/config/target/vhost", /* for hostdev vhost_scsi devices */
489 "/etc/libvirt-sandbox/services/" /* for virt-sandbox service config */
490 };
491
492 const int nropaths = G_N_ELEMENTS(restricted);
493 const int nrwpaths = G_N_ELEMENTS(restricted_rw);
494 const int nopaths = G_N_ELEMENTS(override);
495
496 if (path == NULL) {
497 vah_error(NULL, 0, _("bad pathname"));
498 return -1;
499 }
500
501 /* Don't allow double quotes, since we use them to quote the filename
502 * and this will confuse the apparmor parser.
503 */
504 if (strchr(path, '"') != NULL)
505 return 1;
506
507 /* Require an absolute path */
508 if (STRNEQLEN(path, "/", 1))
509 return 1;
510
511 if (!virFileExists(path))
512 vah_warning(_("path does not exist, skipping file type checks"));
513
514 /* overrides are always allowed */
515 if (array_starts_with(path, override, nopaths) == 0)
516 return 0;
517
518 /* allow read only paths upfront */
519 if (readonly) {
520 if (array_starts_with(path, restricted_rw, nrwpaths) == 0)
521 return 0;
522 }
523
524 /* disallow RW access to all paths in restricted and restriced_rw */
525 if ((array_starts_with(path, restricted, nropaths) == 0 ||
526 array_starts_with(path, restricted_rw, nrwpaths) == 0))
527 return 1;
528
529 return 0;
530 }
531
532 static int
verify_xpath_context(xmlXPathContextPtr ctxt)533 verify_xpath_context(xmlXPathContextPtr ctxt)
534 {
535 char *tmp = NULL;
536
537 if (!ctxt) {
538 vah_warning(_("Invalid context"));
539 return -1;
540 }
541
542 /* check if have <name> */
543 if (!(tmp = virXPathString("string(./name[1])", ctxt))) {
544 vah_warning(_("Could not find <name>"));
545 return -1;
546 }
547 VIR_FREE(tmp);
548
549 /* check if have <uuid> */
550 if (!(tmp = virXPathString("string(./uuid[1])", ctxt))) {
551 vah_warning(_("Could not find <uuid>"));
552 return -1;
553 }
554 VIR_FREE(tmp);
555
556 return 0;
557 }
558
559 /*
560 * Parse the xml we received to fill in the following:
561 * ctl->virtType
562 * ctl->os
563 * ctl->arch
564 *
565 * These are suitable for setting up a virCaps *
566 */
567 static int
caps_mockup(vahControl * ctl,const char * xmlStr)568 caps_mockup(vahControl * ctl, const char *xmlStr)
569 {
570 g_autoptr(xmlDoc) xml = NULL;
571 g_autoptr(xmlXPathContext) ctxt = NULL;
572 char *arch;
573
574 if (!(xml = virXMLParseStringCtxt(xmlStr, _("(domain_definition)"),
575 &ctxt))) {
576 return -1;
577 }
578
579 if (!virXMLNodeNameEqual(ctxt->node, "domain")) {
580 vah_error(NULL, 0, _("unexpected root element, expecting <domain>"));
581 return -1;
582 }
583
584 /* Quick sanity check for some required elements */
585 if (verify_xpath_context(ctxt) != 0)
586 return -1;
587
588 ctl->virtType = virXPathString("string(./@type)", ctxt);
589 if (!ctl->virtType) {
590 vah_error(ctl, 0, _("domain type is not defined"));
591 return -1;
592 }
593 ctl->os = virXPathString("string(./os/type[1])", ctxt);
594 if (!ctl->os) {
595 vah_error(ctl, 0, _("os.type is not defined"));
596 return -1;
597 }
598 arch = virXPathString("string(./os/type[1]/@arch)", ctxt);
599 if (!arch) {
600 ctl->arch = virArchFromHost();
601 } else {
602 ctl->arch = virArchFromString(arch);
603 VIR_FREE(arch);
604 }
605
606 return 0;
607 }
608
609 virDomainDefParserConfig virAAHelperDomainDefParserConfig = {
610 .features = VIR_DOMAIN_DEF_FEATURE_MEMORY_HOTPLUG |
611 VIR_DOMAIN_DEF_FEATURE_OFFLINE_VCPUPIN |
612 VIR_DOMAIN_DEF_FEATURE_INDIVIDUAL_VCPUS |
613 VIR_DOMAIN_DEF_FEATURE_NET_MODEL_STRING,
614 };
615
616 static int
get_definition(vahControl * ctl,const char * xmlStr)617 get_definition(vahControl * ctl, const char *xmlStr)
618 {
619 int ostype, virtType;
620 virCapsGuest *guest; /* this is freed when caps is freed */
621
622 /*
623 * mock up some capabilities. We don't currently use these explicitly,
624 * but need them for virDomainDefParseString().
625 */
626 if (caps_mockup(ctl, xmlStr) != 0)
627 return -1;
628
629 if ((ctl->caps = virCapabilitiesNew(ctl->arch, true, true)) == NULL) {
630 vah_error(ctl, 0, _("could not allocate memory"));
631 return -1;
632 }
633
634 if (!(ctl->xmlopt = virDomainXMLOptionNew(&virAAHelperDomainDefParserConfig,
635 NULL, NULL, NULL, NULL))) {
636 vah_error(ctl, 0, _("Failed to create XML config object"));
637 return -1;
638 }
639
640 if ((ostype = virDomainOSTypeFromString(ctl->os)) < 0) {
641 vah_error(ctl, 0, _("unknown OS type"));
642 return -1;
643 }
644
645 guest = virCapabilitiesAddGuest(ctl->caps, ostype, ctl->arch,
646 NULL, NULL, 0, NULL);
647
648 if ((virtType = virDomainVirtTypeFromString(ctl->virtType)) < 0) {
649 vah_error(ctl, 0, _("unknown virtualization type"));
650 return -1;
651 }
652
653 virCapabilitiesAddGuestDomain(guest, virtType,
654 NULL, NULL, 0, NULL);
655
656 ctl->def = virDomainDefParseString(xmlStr,
657 ctl->xmlopt, NULL,
658 VIR_DOMAIN_DEF_PARSE_SKIP_SECLABEL |
659 VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE);
660
661 if (ctl->def == NULL) {
662 vah_error(ctl, 0, _("could not parse XML"));
663 return -1;
664 }
665
666 if (!ctl->def->name) {
667 vah_error(ctl, 0, _("could not find name in XML"));
668 return -1;
669 }
670
671 if (valid_name(ctl->def->name) != 0) {
672 vah_error(ctl, 0, _("bad name"));
673 return -1;
674 }
675
676 return 0;
677 }
678
679 /**
680 * The permissions allowed are apparmor valid permissions and 'R'. 'R' stands for
681 * read with no explicit deny rule.
682 */
683 static int
vah_add_path(virBuffer * buf,const char * path,const char * perms,bool recursive)684 vah_add_path(virBuffer *buf, const char *path, const char *perms, bool recursive)
685 {
686 char *tmp = NULL;
687 int rc = -1;
688 bool readonly = true;
689 bool explicit_deny_rule = true;
690 char *sub = NULL;
691 char *perms_new = NULL;
692 char *pathdir = NULL;
693 char *pathtmp = NULL;
694 char *pathreal = NULL;
695
696 if (path == NULL)
697 return rc;
698
699 /* Skip files without an absolute path. Not having one confuses the
700 * apparmor parser and this also ensures things like tcp consoles don't
701 * get added to the profile.
702 */
703 if (STRNEQLEN(path, "/", 1)) {
704 vah_warning(path);
705 vah_warning(_("skipped non-absolute path"));
706 return 0;
707 }
708
709 /* files might be created by qemu later on and not exist right now.
710 * But realpath needs a valid path to work on, therefore:
711 * 1. walk the path to find longest valid path
712 * 2. get the realpath of that valid path
713 * 3. re-combine the realpath with the remaining suffix
714 * Note: A totally non existent path is used as-is
715 */
716 pathdir = g_strdup(path);
717 while (!virFileExists(pathdir)) {
718 pathtmp = g_path_get_dirname(pathdir);
719 VIR_FREE(pathdir);
720 pathdir = g_steal_pointer(&pathtmp);
721 }
722
723 if (strlen(pathdir) == 1) {
724 /* nothing of the path does exist yet */
725 tmp = g_strdup(path);
726 } else {
727 pathtmp = g_strdup(path + strlen(pathdir));
728 if ((pathreal = realpath(pathdir, NULL)) == NULL) {
729 vah_error(NULL, 0, pathdir);
730 vah_error(NULL, 0, _("could not find realpath"));
731 goto cleanup;
732 }
733 tmp = g_strdup_printf("%s%s", pathreal, pathtmp);
734 }
735
736 perms_new = g_strdup(perms);
737
738 if (strchr(perms_new, 'w') != NULL) {
739 readonly = false;
740 explicit_deny_rule = false;
741 }
742
743 if ((sub = strchr(perms_new, 'R')) != NULL) {
744 /* Don't write the invalid R permission, replace it with 'r' */
745 sub[0] = 'r';
746 explicit_deny_rule = false;
747 }
748
749 rc = valid_path(tmp, readonly);
750 if (rc != 0) {
751 if (rc > 0) {
752 vah_error(NULL, 0, path);
753 vah_error(NULL, 0, _("skipped restricted file"));
754 }
755 goto cleanup;
756 }
757
758 if (tmp[strlen(tmp) - 1] == '/')
759 tmp[strlen(tmp) - 1] = '\0';
760
761 virBufferAsprintf(buf, " \"%s%s\" %s,\n", tmp, recursive ? "/**" : "",
762 perms_new);
763 if (explicit_deny_rule) {
764 virBufferAddLit(buf, " # don't audit writes to readonly files\n");
765 virBufferAsprintf(buf, " deny \"%s%s\" w,\n", tmp, recursive ? "/**" : "");
766 }
767 if (recursive) {
768 /* allow reading (but not creating) the dir */
769 virBufferAsprintf(buf, " \"%s/\" r,\n", tmp);
770 }
771
772 cleanup:
773 VIR_FREE(pathdir);
774 VIR_FREE(pathtmp);
775 VIR_FREE(pathreal);
776 VIR_FREE(perms_new);
777 VIR_FREE(tmp);
778
779 return rc;
780 }
781
782 static int
vah_add_file(virBuffer * buf,const char * path,const char * perms)783 vah_add_file(virBuffer *buf, const char *path, const char *perms)
784 {
785 return vah_add_path(buf, path, perms, false);
786 }
787
788 static int
vah_add_file_chardev(virBuffer * buf,const char * path,const char * perms,const int type)789 vah_add_file_chardev(virBuffer *buf,
790 const char *path,
791 const char *perms,
792 const int type)
793 {
794 char *pipe_in;
795 char *pipe_out;
796 int rc = -1;
797
798 if (type == VIR_DOMAIN_CHR_TYPE_PIPE) {
799 /* add the pipe input */
800 pipe_in = g_strdup_printf("%s.in", path);
801
802 if (vah_add_file(buf, pipe_in, perms) != 0)
803 goto clean_pipe_in;
804
805 /* add the pipe output */
806 pipe_out = g_strdup_printf("%s.out", path);
807
808 if (vah_add_file(buf, pipe_out, perms) != 0)
809 goto clean_pipe_out;
810
811 rc = 0;
812 clean_pipe_out:
813 VIR_FREE(pipe_out);
814 clean_pipe_in:
815 VIR_FREE(pipe_in);
816 } else {
817 /* add the file */
818 if (vah_add_file(buf, path, perms) != 0)
819 return -1;
820 rc = 0;
821 }
822
823 return rc;
824 }
825
826 static int
file_iterate_hostdev_cb(virUSBDevice * dev G_GNUC_UNUSED,const char * file,void * opaque)827 file_iterate_hostdev_cb(virUSBDevice *dev G_GNUC_UNUSED,
828 const char *file, void *opaque)
829 {
830 virBuffer *buf = opaque;
831 return vah_add_file(buf, file, "rw");
832 }
833
834 static int
file_iterate_pci_cb(virPCIDevice * dev G_GNUC_UNUSED,const char * file,void * opaque)835 file_iterate_pci_cb(virPCIDevice *dev G_GNUC_UNUSED,
836 const char *file, void *opaque)
837 {
838 virBuffer *buf = opaque;
839 return vah_add_file(buf, file, "rw");
840 }
841
842 static int
add_file_path(virStorageSource * src,size_t depth,virBuffer * buf)843 add_file_path(virStorageSource *src,
844 size_t depth,
845 virBuffer *buf)
846 {
847 int ret;
848
849 /* execute the callback only for local storage */
850 if (!src->path || !virStorageSourceIsLocalStorage(src))
851 return 0;
852
853 if (depth == 0) {
854 if (src->readonly)
855 ret = vah_add_file(buf, src->path, "rk");
856 else
857 ret = vah_add_file(buf, src->path, "rwk");
858 } else {
859 ret = vah_add_file(buf, src->path, "rk");
860 }
861
862 if (ret != 0)
863 ret = -1;
864
865 return ret;
866 }
867
868
869 static int
storage_source_add_files(virStorageSource * src,virBuffer * buf,size_t depth)870 storage_source_add_files(virStorageSource *src,
871 virBuffer *buf,
872 size_t depth)
873 {
874 virStorageSource *tmp;
875
876 for (tmp = src; virStorageSourceIsBacking(tmp); tmp = tmp->backingStore) {
877 if (add_file_path(tmp, depth, buf) < 0)
878 return -1;
879
880 depth++;
881 }
882
883 return 0;
884 }
885
886 static int
get_files(vahControl * ctl)887 get_files(vahControl * ctl)
888 {
889 g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
890 int rc = -1;
891 size_t i;
892 char *uuid;
893 char *mem_path = NULL;
894 char uuidstr[VIR_UUID_STRING_BUFLEN];
895 bool needsVfio = false, needsvhost = false, needsgl = false;
896
897 /* verify uuid is same as what we were given on the command line */
898 virUUIDFormat(ctl->def->uuid, uuidstr);
899 uuid = g_strdup_printf("%s%s", AA_PREFIX, uuidstr);
900
901 if (STRNEQ(uuid, ctl->uuid)) {
902 vah_error(ctl, 0, _("given uuid does not match XML uuid"));
903 goto cleanup;
904 }
905
906 /* load the storage driver so that backing store can be accessed */
907 #ifdef WITH_STORAGE
908 virDriverLoadModule("storage", "storageRegister", false);
909 #endif
910
911 for (i = 0; i < ctl->def->ndisks; i++) {
912 virDomainDiskDef *disk = ctl->def->disks[i];
913
914 if (virStorageSourceIsEmpty(disk->src))
915 continue;
916 /* XXX - if we knew the qemu user:group here we could send it in
917 * so that the open could be re-tried as that user:group.
918 *
919 * The maximum depth is limited to 200 layers similarly to the qemu
920 * implementation.
921 */
922 if (!disk->src->backingStore)
923 virStorageSourceGetMetadata(disk->src, -1, -1, 200, false);
924
925 /* XXX should handle open errors more careful than just ignoring them.
926 */
927 if (storage_source_add_files(disk->src, &buf, 0) < 0)
928 goto cleanup;
929 }
930
931 for (i = 0; i < ctl->def->nserials; i++)
932 if (ctl->def->serials[i] &&
933 (ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_PTY ||
934 ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_DEV ||
935 ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_FILE ||
936 ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_UNIX ||
937 ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_PIPE) &&
938 ctl->def->serials[i]->source->data.file.path &&
939 ctl->def->serials[i]->source->data.file.path[0] != '\0')
940 if (vah_add_file_chardev(&buf,
941 ctl->def->serials[i]->source->data.file.path,
942 "rw",
943 ctl->def->serials[i]->source->type) != 0)
944 goto cleanup;
945
946 for (i = 0; i < ctl->def->nconsoles; i++)
947 if (ctl->def->consoles[i] &&
948 (ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_PTY ||
949 ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_DEV ||
950 ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_FILE ||
951 ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_UNIX ||
952 ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_PIPE) &&
953 ctl->def->consoles[i]->source->data.file.path &&
954 ctl->def->consoles[i]->source->data.file.path[0] != '\0')
955 if (vah_add_file(&buf,
956 ctl->def->consoles[i]->source->data.file.path, "rw") != 0)
957 goto cleanup;
958
959 for (i = 0; i < ctl->def->nparallels; i++)
960 if (ctl->def->parallels[i] &&
961 (ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_PTY ||
962 ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_DEV ||
963 ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_FILE ||
964 ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_UNIX ||
965 ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_PIPE) &&
966 ctl->def->parallels[i]->source->data.file.path &&
967 ctl->def->parallels[i]->source->data.file.path[0] != '\0')
968 if (vah_add_file_chardev(&buf,
969 ctl->def->parallels[i]->source->data.file.path,
970 "rw",
971 ctl->def->parallels[i]->source->type) != 0)
972 goto cleanup;
973
974 for (i = 0; i < ctl->def->nchannels; i++)
975 if (ctl->def->channels[i] &&
976 (ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_PTY ||
977 ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_DEV ||
978 ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_FILE ||
979 ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_UNIX ||
980 ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_PIPE) &&
981 ctl->def->channels[i]->source->data.file.path &&
982 ctl->def->channels[i]->source->data.file.path[0] != '\0')
983 if (vah_add_file_chardev(&buf,
984 ctl->def->channels[i]->source->data.file.path,
985 "rw",
986 ctl->def->channels[i]->source->type) != 0)
987 goto cleanup;
988
989 if (ctl->def->os.kernel)
990 if (vah_add_file(&buf, ctl->def->os.kernel, "r") != 0)
991 goto cleanup;
992
993 if (ctl->def->os.initrd)
994 if (vah_add_file(&buf, ctl->def->os.initrd, "r") != 0)
995 goto cleanup;
996
997 if (ctl->def->os.dtb)
998 if (vah_add_file(&buf, ctl->def->os.dtb, "r") != 0)
999 goto cleanup;
1000
1001 if (ctl->def->os.slic_table)
1002 if (vah_add_file(&buf, ctl->def->os.slic_table, "r") != 0)
1003 goto cleanup;
1004
1005 if (ctl->def->os.loader && ctl->def->os.loader->path)
1006 if (vah_add_file(&buf, ctl->def->os.loader->path, "rk") != 0)
1007 goto cleanup;
1008
1009 if (ctl->def->os.loader && ctl->def->os.loader->nvram)
1010 if (vah_add_file(&buf, ctl->def->os.loader->nvram, "rwk") != 0)
1011 goto cleanup;
1012
1013 for (i = 0; i < ctl->def->ngraphics; i++) {
1014 virDomainGraphicsDef *graphics = ctl->def->graphics[i];
1015 size_t n;
1016 const char *rendernode = virDomainGraphicsGetRenderNode(graphics);
1017
1018 if (rendernode) {
1019 vah_add_file(&buf, rendernode, "rw");
1020 needsgl = true;
1021 } else {
1022 if (virDomainGraphicsNeedsAutoRenderNode(graphics)) {
1023 char *defaultRenderNode = virHostGetDRMRenderNode();
1024 needsgl = true;
1025
1026 if (defaultRenderNode) {
1027 vah_add_file(&buf, defaultRenderNode, "rw");
1028 VIR_FREE(defaultRenderNode);
1029 }
1030 }
1031 }
1032
1033 for (n = 0; n < graphics->nListens; n++) {
1034 virDomainGraphicsListenDef listenObj = graphics->listens[n];
1035
1036 if (listenObj.type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET &&
1037 listenObj.socket &&
1038 vah_add_file(&buf, listenObj.socket, "rw"))
1039 goto cleanup;
1040 }
1041 }
1042
1043 if (ctl->def->ngraphics == 1 &&
1044 ctl->def->graphics[0]->type == VIR_DOMAIN_GRAPHICS_TYPE_SDL)
1045 if (vah_add_file(&buf, ctl->def->graphics[0]->data.sdl.xauth,
1046 "r") != 0)
1047 goto cleanup;
1048
1049 for (i = 0; i < ctl->def->nhostdevs; i++)
1050 if (ctl->def->hostdevs[i]) {
1051 virDomainHostdevDef *dev = ctl->def->hostdevs[i];
1052 virDomainHostdevSubsysUSB *usbsrc = &dev->source.subsys.u.usb;
1053 switch (dev->source.subsys.type) {
1054 case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: {
1055 virUSBDevice *usb =
1056 virUSBDeviceNew(usbsrc->bus, usbsrc->device, NULL);
1057
1058 if (usb == NULL)
1059 continue;
1060
1061 if (virHostdevFindUSBDevice(dev, true, &usb) < 0)
1062 continue;
1063
1064 rc = virUSBDeviceFileIterate(usb, file_iterate_hostdev_cb, &buf);
1065 virUSBDeviceFree(usb);
1066 if (rc != 0)
1067 goto cleanup;
1068 break;
1069 }
1070
1071 case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: {
1072 virDomainHostdevSubsysMediatedDev *mdevsrc = &dev->source.subsys.u.mdev;
1073 switch ((virMediatedDeviceModelType) mdevsrc->model) {
1074 case VIR_MDEV_MODEL_TYPE_VFIO_PCI:
1075 case VIR_MDEV_MODEL_TYPE_VFIO_AP:
1076 case VIR_MDEV_MODEL_TYPE_VFIO_CCW:
1077 needsVfio = true;
1078 break;
1079 case VIR_MDEV_MODEL_TYPE_LAST:
1080 default:
1081 virReportEnumRangeError(virMediatedDeviceModelType,
1082 mdevsrc->model);
1083 break;
1084 }
1085 break;
1086 }
1087
1088 case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: {
1089 virPCIDevice *pci = virPCIDeviceNew(&dev->source.subsys.u.pci.addr);
1090
1091 virDomainHostdevSubsysPCIBackendType backend = dev->source.subsys.u.pci.backend;
1092 if (backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO ||
1093 backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT) {
1094 needsVfio = true;
1095 }
1096
1097 if (pci == NULL)
1098 continue;
1099
1100 rc = virPCIDeviceFileIterate(pci, file_iterate_pci_cb, &buf);
1101 virPCIDeviceFree(pci);
1102
1103 break;
1104 }
1105
1106 default:
1107 rc = 0;
1108 break;
1109 } /* switch */
1110 }
1111
1112 for (i = 0; i < ctl->def->nfss; i++) {
1113 if (ctl->def->fss[i] &&
1114 ctl->def->fss[i]->type == VIR_DOMAIN_FS_TYPE_MOUNT &&
1115 (ctl->def->fss[i]->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_PATH ||
1116 ctl->def->fss[i]->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_DEFAULT) &&
1117 ctl->def->fss[i]->src) {
1118 virDomainFSDef *fs = ctl->def->fss[i];
1119
1120 /* We don't need to add deny rw rules for readonly mounts,
1121 * this can only lead to troubles when mounting / readonly.
1122 */
1123 if (vah_add_path(&buf, fs->src->path, fs->readonly ? "R" : "rwl", true) != 0)
1124 goto cleanup;
1125 }
1126 }
1127
1128 for (i = 0; i < ctl->def->ninputs; i++) {
1129 if (ctl->def->inputs[i] &&
1130 (ctl->def->inputs[i]->type == VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH ||
1131 ctl->def->inputs[i]->type == VIR_DOMAIN_INPUT_TYPE_EVDEV)) {
1132 if (vah_add_file(&buf, ctl->def->inputs[i]->source.evdev, "rw") != 0)
1133 goto cleanup;
1134 }
1135 }
1136
1137 for (i = 0; i < ctl->def->nnets; i++) {
1138 if (ctl->def->nets[i] &&
1139 ctl->def->nets[i]->type == VIR_DOMAIN_NET_TYPE_VHOSTUSER &&
1140 ctl->def->nets[i]->data.vhostuser) {
1141 virDomainChrSourceDef *vhu = ctl->def->nets[i]->data.vhostuser;
1142
1143 if (vah_add_file_chardev(&buf, vhu->data.nix.path, "rw",
1144 vhu->type) != 0)
1145 goto cleanup;
1146 }
1147 }
1148
1149 for (i = 0; i < ctl->def->nmems; i++) {
1150 if (ctl->def->mems[i] &&
1151 ctl->def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM) {
1152 if (vah_add_file(&buf, ctl->def->mems[i]->nvdimmPath, "rw") != 0)
1153 goto cleanup;
1154 }
1155 }
1156
1157 for (i = 0; i < ctl->def->nsysinfo; i++) {
1158 size_t j;
1159
1160 for (j = 0; j < ctl->def->sysinfo[i]->nfw_cfgs; j++) {
1161 virSysinfoFWCfgDef *f = &ctl->def->sysinfo[i]->fw_cfgs[j];
1162
1163 if (f->file &&
1164 vah_add_file(&buf, f->file, "r") != 0)
1165 goto cleanup;
1166 }
1167 }
1168
1169 for (i = 0; i < ctl->def->nshmems; i++) {
1170 virDomainShmemDef *shmem = ctl->def->shmems[i];
1171 /* explicit server paths can be on any model to overwrites defaults.
1172 * When the server path is enabled, use it - otherwise fallback to
1173 * model dependent defaults. */
1174 if (shmem->server.enabled &&
1175 shmem->server.chr->data.nix.path) {
1176 if (vah_add_file(&buf, shmem->server.chr->data.nix.path,
1177 "rw") != 0)
1178 goto cleanup;
1179 } else {
1180 switch (shmem->model) {
1181 case VIR_DOMAIN_SHMEM_MODEL_IVSHMEM_PLAIN:
1182 /* until exposed, recreate qemuBuildShmemBackendMemProps */
1183 mem_path = g_strdup_printf("/dev/shm/%s", shmem->name);
1184 break;
1185 case VIR_DOMAIN_SHMEM_MODEL_IVSHMEM_DOORBELL:
1186 case VIR_DOMAIN_SHMEM_MODEL_IVSHMEM:
1187 /* until exposed, recreate qemuDomainPrepareShmemChardev */
1188 mem_path = g_strdup_printf("/var/lib/libvirt/shmem-%s-sock",
1189 shmem->name);
1190 break;
1191 case VIR_DOMAIN_SHMEM_MODEL_LAST:
1192 virReportEnumRangeError(virDomainShmemModel,
1193 shmem->model);
1194 break;
1195 }
1196 if (mem_path != NULL) {
1197 if (vah_add_file(&buf, mem_path, "rw") != 0)
1198 goto cleanup;
1199 }
1200 }
1201 }
1202
1203
1204 if (ctl->def->ntpms > 0) {
1205 char *shortName = NULL;
1206 const char *tpmpath = NULL;
1207
1208 for (i = 0; i < ctl->def->ntpms; i++) {
1209 if (ctl->def->tpms[i]->type != VIR_DOMAIN_TPM_TYPE_EMULATOR)
1210 continue;
1211
1212 shortName = virDomainDefGetShortName(ctl->def);
1213
1214 switch (ctl->def->tpms[i]->version) {
1215 case VIR_DOMAIN_TPM_VERSION_1_2:
1216 tpmpath = "tpm1.2";
1217 break;
1218 case VIR_DOMAIN_TPM_VERSION_2_0:
1219 tpmpath = "tpm2";
1220 break;
1221 case VIR_DOMAIN_TPM_VERSION_DEFAULT:
1222 case VIR_DOMAIN_TPM_VERSION_LAST:
1223 break;
1224 }
1225
1226 /* Unix socket for QEMU and swtpm to use */
1227 virBufferAsprintf(&buf,
1228 " \"%s/libvirt/qemu/swtpm/%s-swtpm.sock\" rw,\n",
1229 RUNSTATEDIR, shortName);
1230 /* Paths for swtpm to use: give it access to its state
1231 * directory (state files and fsync on dir), log, and PID files.
1232 */
1233 virBufferAsprintf(&buf,
1234 " \"%s/lib/libvirt/swtpm/%s/%s/\" r,\n",
1235 LOCALSTATEDIR, uuidstr, tpmpath);
1236 virBufferAsprintf(&buf,
1237 " \"%s/lib/libvirt/swtpm/%s/%s/**\" rwk,\n",
1238 LOCALSTATEDIR, uuidstr, tpmpath);
1239 virBufferAsprintf(&buf,
1240 " \"%s/log/swtpm/libvirt/qemu/%s-swtpm.log\" w,\n",
1241 LOCALSTATEDIR, ctl->def->name);
1242 virBufferAsprintf(&buf,
1243 " \"%s/libvirt/qemu/swtpm/%s-swtpm.pid\" rw,\n",
1244 RUNSTATEDIR, shortName);
1245
1246 VIR_FREE(shortName);
1247 }
1248 }
1249
1250 for (i = 0; i < ctl->def->nsmartcards; i++) {
1251 virDomainSmartcardDef *sc = ctl->def->smartcards[i];
1252 virDomainSmartcardType sc_type = sc->type;
1253 char *sc_db = (char *)VIR_DOMAIN_SMARTCARD_DEFAULT_DATABASE;
1254 if (sc->data.cert.database)
1255 sc_db = sc->data.cert.database;
1256 switch (sc_type) {
1257 /*
1258 * Note: At time of writing, to get this working, qemu seccomp sandbox has
1259 * to be disabled or the host must be running QEMU with commit
1260 * 9a1565a03b79d80b236bc7cc2dbce52a2ef3a1b8.
1261 * It's possibly due to libcacard:vcard_emul_new_event_thread(), which calls
1262 * PR_CreateThread(), which calls {g,s}etpriority(). And resourcecontrol seccomp
1263 * filter forbids it (cf src/qemu/qemu_command.c which seems to always use
1264 * resourcecontrol=deny).
1265 */
1266 case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
1267 virBufferAddLit(&buf, " \"/etc/pki/nssdb/{,*}\" rk,\n");
1268 break;
1269 case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
1270 virBufferAsprintf(&buf, " \"%s/{,*}\" rk,\n", sc_db);
1271 break;
1272 /*
1273 * Nothing to do for passthrough, as the smartcard
1274 * access is done through TCP or Spice
1275 */
1276 case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
1277 break;
1278 case VIR_DOMAIN_SMARTCARD_TYPE_LAST:
1279 break;
1280 }
1281 }
1282
1283 if (ctl->def->virtType == VIR_DOMAIN_VIRT_KVM) {
1284 for (i = 0; i < ctl->def->nnets; i++) {
1285 virDomainNetDef *net = ctl->def->nets[i];
1286 if (net && virDomainNetGetModelString(net)) {
1287 if (net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_QEMU)
1288 continue;
1289 if (!virDomainNetIsVirtioModel(net))
1290 continue;
1291 }
1292 needsvhost = true;
1293 }
1294 }
1295 if (needsvhost)
1296 virBufferAddLit(&buf, " \"/dev/vhost-net\" rw,\n");
1297
1298 if (needsVfio) {
1299 virBufferAddLit(&buf, " \"/dev/vfio/vfio\" rw,\n");
1300 virBufferAddLit(&buf, " \"/dev/vfio/[0-9]*\" rw,\n");
1301 }
1302 if (needsgl) {
1303 /* if using gl all sorts of further dri related paths will be needed */
1304 virBufferAddLit(&buf, " # DRI/Mesa/(e)GL config and driver paths\n");
1305 virBufferAddLit(&buf, " \"/usr/lib{,32,64}/dri/*.so*\" mr,\n");
1306 virBufferAddLit(&buf, " \"/usr/lib/@{multiarch}/dri/*.so*\" mr,\n");
1307 virBufferAddLit(&buf, " \"/usr/lib/fglrx/dri/*.so*\" mr,\n");
1308 virBufferAddLit(&buf, " \"/etc/drirc\" r,\n");
1309 virBufferAddLit(&buf, " \"/usr/share/drirc.d/{,*.conf}\" r,\n");
1310 virBufferAddLit(&buf, " \"/etc/glvnd/egl_vendor.d/{,*}\" r,\n");
1311 virBufferAddLit(&buf, " \"/usr/share/glvnd/egl_vendor.d/{,*}\" r,\n");
1312 virBufferAddLit(&buf, " \"/usr/share/egl/egl_external_platform.d/\" r,\n");
1313 virBufferAddLit(&buf, " \"/usr/share/egl/egl_external_platform.d/*\" r,\n");
1314 virBufferAddLit(&buf, " \"/proc/modules\" r,\n");
1315 virBufferAddLit(&buf, " \"/proc/driver/nvidia/params\" r,\n");
1316 virBufferAddLit(&buf, " \"/dev/nvidiactl\" rw,\n");
1317 virBufferAddLit(&buf, " # Probe DRI device attributes\n");
1318 virBufferAddLit(&buf, " \"/dev/dri/\" r,\n");
1319 virBufferAddLit(&buf, " \"/sys/devices/**/{uevent,vendor,device,subsystem_vendor,subsystem_device}\" r,\n");
1320 virBufferAddLit(&buf, " # dri libs will trigger that, but t is not requited and DAC would deny it anyway\n");
1321 virBufferAddLit(&buf, " deny \"/var/lib/libvirt/.cache/\" w,\n");
1322 }
1323
1324 if (ctl->newfile)
1325 if (vah_add_file(&buf, ctl->newfile, "rwk") != 0)
1326 goto cleanup;
1327
1328 rc = 0;
1329 ctl->files = virBufferContentAndReset(&buf);
1330
1331 cleanup:
1332 VIR_FREE(mem_path);
1333 VIR_FREE(uuid);
1334 return rc;
1335 }
1336
1337 static int
vahParseArgv(vahControl * ctl,int argc,char ** argv)1338 vahParseArgv(vahControl * ctl, int argc, char **argv)
1339 {
1340 int arg, idx = 0;
1341 struct option opt[] = {
1342 {"add", 0, 0, 'a'},
1343 {"create", 0, 0, 'c'},
1344 {"dryrun", 0, 0, 'd'},
1345 {"delete", 0, 0, 'D'},
1346 {"add-file", 0, 0, 'f'},
1347 {"append-file", 0, 0, 'F'},
1348 {"help", 0, 0, 'h'},
1349 {"replace", 0, 0, 'r'},
1350 {"remove", 0, 0, 'R'},
1351 {"uuid", 1, 0, 'u'},
1352 {0, 0, 0, 0}
1353 };
1354
1355 while ((arg = getopt_long(argc, argv, "acdDhrRH:b:u:p:f:F:", opt,
1356 &idx)) != -1) {
1357 switch (arg) {
1358 case 'a':
1359 ctl->cmd = 'a';
1360 break;
1361 case 'c':
1362 ctl->cmd = 'c';
1363 break;
1364 case 'd':
1365 ctl->dryrun = true;
1366 break;
1367 case 'D':
1368 ctl->cmd = 'D';
1369 break;
1370 case 'f':
1371 case 'F':
1372 ctl->newfile = g_strdup(optarg);
1373 ctl->append = arg == 'F';
1374 break;
1375 case 'h':
1376 vah_usage();
1377 exit(EXIT_SUCCESS);
1378 break;
1379 case 'r':
1380 ctl->cmd = 'r';
1381 break;
1382 case 'R':
1383 ctl->cmd = 'R';
1384 break;
1385 case 'u':
1386 if (strlen(optarg) > PROFILE_NAME_SIZE - 1)
1387 vah_error(ctl, 1, _("invalid UUID"));
1388 if (virStrcpy((char *)ctl->uuid, optarg,
1389 PROFILE_NAME_SIZE) < 0)
1390 vah_error(ctl, 1, _("error copying UUID"));
1391 break;
1392 default:
1393 vah_error(ctl, 1, _("unsupported option"));
1394 break;
1395 }
1396 }
1397 if (strchr("acDrR", ctl->cmd) == NULL)
1398 vah_error(ctl, 1, _("bad command"));
1399
1400 if (valid_uuid(ctl->uuid) != 0)
1401 vah_error(ctl, 1, _("invalid UUID"));
1402
1403 if (!ctl->cmd) {
1404 vah_usage();
1405 exit(EXIT_FAILURE);
1406 }
1407
1408 if (ctl->cmd == 'c' || ctl->cmd == 'r') {
1409 char *xmlStr = NULL;
1410 if (virFileReadLimFD(STDIN_FILENO, MAX_FILE_LEN, &xmlStr) < 0)
1411 vah_error(ctl, 1, _("could not read xml file"));
1412
1413 if (get_definition(ctl, xmlStr) != 0 || ctl->def == NULL) {
1414 VIR_FREE(xmlStr);
1415 vah_error(ctl, 1, _("could not get VM definition"));
1416 }
1417 VIR_FREE(xmlStr);
1418
1419 if (get_files(ctl) != 0)
1420 vah_error(ctl, 1, _("invalid VM definition"));
1421 }
1422 return 0;
1423 }
1424
1425
1426 /*
1427 * virt-aa-helper -c -u UUID < file.xml
1428 * virt-aa-helper -r -u UUID [-f <file>] < file.xml
1429 * virt-aa-helper -a -u UUID
1430 * virt-aa-helper -R -u UUID
1431 * virt-aa-helper -D -u UUID
1432 */
1433 int
main(int argc,char ** argv)1434 main(int argc, char **argv)
1435 {
1436 vahControl _ctl, *ctl = &_ctl;
1437 int rc = -1;
1438 char *profile = NULL;
1439 char *include_file = NULL;
1440 off_t size;
1441 bool purged = 0;
1442
1443 if (virGettextInitialize() < 0 ||
1444 virErrorInitialize() < 0) {
1445 fprintf(stderr, _("%s: initialization failed\n"), argv[0]);
1446 exit(EXIT_FAILURE);
1447 }
1448
1449 virFileActivateDirOverrideForProg(argv[0]);
1450
1451 /* Initialize the log system */
1452 virLogSetFromEnv();
1453
1454 /* clear the environment */
1455 environ = NULL;
1456 if (g_setenv("PATH", "/sbin:/usr/sbin", TRUE) == FALSE)
1457 vah_error(ctl, 1, _("could not set PATH"));
1458
1459 /* ensure the traditional IFS setting */
1460 if (g_setenv("IFS", " \t\n", TRUE) == FALSE)
1461 vah_error(ctl, 1, _("could not set IFS"));
1462
1463 if (!(progname = strrchr(argv[0], '/')))
1464 progname = argv[0];
1465 else
1466 progname++;
1467
1468 memset(ctl, 0, sizeof(vahControl));
1469
1470 if (vahParseArgv(ctl, argc, argv) != 0)
1471 vah_error(ctl, 1, _("could not parse arguments"));
1472
1473 profile = g_strdup_printf("%s/%s", APPARMOR_DIR "/libvirt", ctl->uuid);
1474 include_file = g_strdup_printf("%s/%s.files", APPARMOR_DIR "/libvirt", ctl->uuid);
1475
1476 if (ctl->cmd == 'a') {
1477 rc = parserLoad(ctl->uuid);
1478 } else if (ctl->cmd == 'R' || ctl->cmd == 'D') {
1479 rc = parserRemove(ctl->uuid);
1480 if (ctl->cmd == 'D')
1481 unlink(include_file);
1482 } else if (ctl->cmd == 'c' || ctl->cmd == 'r') {
1483 char *included_files = NULL;
1484 g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
1485
1486 if (ctl->cmd == 'c' && virFileExists(profile))
1487 vah_error(ctl, 1, _("profile exists"));
1488
1489 /*
1490 * Rare cases can leave corrupted empty files behind breaking
1491 * the guest. An empty file is never correct as virt-aa-helper
1492 * would at least add the basic rules, therefore clean this up
1493 * for a proper refresh.
1494 */
1495 if (virFileExists(profile)) {
1496 size = virFileLength(profile, -1);
1497 if (size == 0) {
1498 vah_warning(_("Profile of 0 size detected, will attempt to remove it"));
1499 if ((rc = parserRemove(ctl->uuid) != 0))
1500 vah_error(ctl, 1, _("could not remove profile"));
1501 unlink(profile);
1502 purged = true;
1503 }
1504 }
1505 if (ctl->append && ctl->newfile) {
1506 if (vah_add_file(&buf, ctl->newfile, "rwk") != 0)
1507 goto cleanup;
1508 } else {
1509 if (ctl->def->virtType == VIR_DOMAIN_VIRT_QEMU ||
1510 ctl->def->virtType == VIR_DOMAIN_VIRT_KQEMU ||
1511 ctl->def->virtType == VIR_DOMAIN_VIRT_KVM) {
1512 virBufferAsprintf(&buf, " \"%s/log/libvirt/**/%s.log\" w,\n",
1513 LOCALSTATEDIR, ctl->def->name);
1514 virBufferAsprintf(&buf, " \"%s/lib/libvirt/qemu/domain-%s/monitor.sock\" rw,\n",
1515 LOCALSTATEDIR, ctl->def->name);
1516 virBufferAsprintf(&buf, " \"%s/lib/libvirt/qemu/domain-%d-%.*s/*\" rw,\n",
1517 LOCALSTATEDIR, ctl->def->id, 20, ctl->def->name);
1518 virBufferAsprintf(&buf, " \"%s/libvirt/**/%s.pid\" rwk,\n",
1519 RUNSTATEDIR, ctl->def->name);
1520 virBufferAsprintf(&buf, " \"%s/libvirt/**/*.tunnelmigrate.dest.%s\" rw,\n",
1521 RUNSTATEDIR, ctl->def->name);
1522 }
1523 if (ctl->files)
1524 virBufferAdd(&buf, ctl->files, -1);
1525 }
1526
1527 included_files = virBufferContentAndReset(&buf);
1528
1529 /* (re)create the include file using included_files */
1530 if (ctl->dryrun) {
1531 vah_info(include_file);
1532 vah_info(included_files);
1533 rc = 0;
1534 } else if (ctl->def->virtType == VIR_DOMAIN_VIRT_LXC) {
1535 rc = 0;
1536 } else if ((rc = update_include_file(include_file,
1537 included_files,
1538 ctl->append)) != 0) {
1539 goto cleanup;
1540 }
1541
1542
1543 /* create the profile from TEMPLATE */
1544 if (ctl->cmd == 'c' || purged) {
1545 char *tmp = NULL;
1546 tmp = g_strdup_printf(" #include <libvirt/%s.files>\n", ctl->uuid);
1547
1548 if (ctl->dryrun) {
1549 vah_info(profile);
1550 vah_info(ctl->uuid);
1551 vah_info(tmp);
1552 rc = 0;
1553 } else if ((rc = create_profile(profile, ctl->uuid, tmp,
1554 ctl->def->virtType)) != 0) {
1555 vah_error(ctl, 0, _("could not create profile"));
1556 unlink(include_file);
1557 }
1558 VIR_FREE(tmp);
1559 }
1560
1561 if (rc == 0 && !ctl->dryrun) {
1562 if (ctl->cmd == 'c')
1563 rc = parserLoad(ctl->uuid);
1564 else
1565 rc = parserReplace(ctl->uuid);
1566
1567 /* cleanup */
1568 if (rc != 0) {
1569 unlink(include_file);
1570 if (ctl->cmd == 'c')
1571 unlink(profile);
1572 }
1573 }
1574 cleanup:
1575 VIR_FREE(included_files);
1576 }
1577
1578 vahDeinit(ctl);
1579
1580 VIR_FREE(profile);
1581 VIR_FREE(include_file);
1582
1583 exit(rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
1584 }
1585