virt-aa-helper.c 49.1 KB
Newer Older
J
Jamie Strandboge 已提交
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.
J
Jamie Strandboge 已提交
6
 *
O
Osier Yang 已提交
7 8 9 10 11 12 13 14 15 16 17
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library.  If not, see
O
Osier Yang 已提交
19
 * <http://www.gnu.org/licenses/>.
J
Jamie Strandboge 已提交
20 21 22 23 24 25 26 27 28 29 30 31
 */

#include <config.h>

#include <stdarg.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/utsname.h>

#include "internal.h"
32
#include "virbuffer.h"
33
#include "viralloc.h"
34
#include "vircommand.h"
35
#include "virlog.h"
36
#include "driver.h"
J
Jamie Strandboge 已提交
37 38 39 40

#include "security_driver.h"
#include "security_apparmor.h"
#include "domain_conf.h"
41
#include "virxml.h"
42
#include "viruuid.h"
43
#include "virusb.h"
44
#include "virutil.h"
45
#include "virpci.h"
E
Eric Blake 已提交
46
#include "virfile.h"
47
#include "configmake.h"
48
#include "virrandom.h"
49
#include "virstring.h"
50
#include "virgettext.h"
51
#include "virhostdev.h"
J
Jamie Strandboge 已提交
52

53 54
#define VIR_FROM_THIS VIR_FROM_SECURITY

J
Jamie Strandboge 已提交
55 56 57 58 59 60 61 62 63 64 65 66
static char *progname;

typedef struct {
    char uuid[PROFILE_NAME_SIZE];       /* UUID of vm */
    bool dryrun;                /* dry run */
    char cmd;                   /* 'c'   create
                                 * 'a'   add (load)
                                 * 'r'   replace
                                 * 'R'   remove */
    char *files;                /* list of files */
    virDomainDefPtr def;        /* VM definition */
    virCapsPtr caps;            /* VM capabilities */
67
    virDomainXMLOptionPtr xmlopt; /* XML parser data */
68
    char *virtType;                  /* type of hypervisor (eg qemu, xen, lxc) */
69
    char *os;                   /* type of os (eg hvm, xen, exe) */
70
    virArch arch;               /* machine architecture */
71 72
    char *newfile;              /* newly added file */
    bool append;                /* append to .files instead of rewrite */
J
Jamie Strandboge 已提交
73 74 75 76 77 78 79 80 81
} vahControl;

static int
vahDeinit(vahControl * ctl)
{
    if (ctl == NULL)
        return -1;

    VIR_FREE(ctl->def);
82
    virObjectUnref(ctl->caps);
83
    virObjectUnref(ctl->xmlopt);
84
    VIR_FREE(ctl->files);
85
    VIR_FREE(ctl->virtType);
86
    VIR_FREE(ctl->os);
87
    VIR_FREE(ctl->newfile);
J
Jamie Strandboge 已提交
88 89 90 91 92 93 94 95 96 97

    return 0;
}

/*
 * Print usage
 */
static void
vah_usage(void)
{
98 99
    printf(_("\n%s mode [options] [extra file] [< def.xml]\n\n"
            "  Modes:\n"
100 101 102 103 104
            "    -a | --add                     load profile\n"
            "    -c | --create                  create profile from template\n"
            "    -D | --delete                  unload and delete profile\n"
            "    -r | --replace                 reload profile\n"
            "    -R | --remove                  unload profile\n"
105 106
            "  Options:\n"
            "    -d | --dryrun                  dry run\n"
107
            "    -u | --uuid <uuid>             uuid (profile name)\n"
108 109 110 111
            "    -h | --help                    this help\n"
            "  Extra File:\n"
            "    -f | --add-file <file>         add file to a profile generated from XML\n"
            "    -F | --append-file <file>      append file to an existing profile\n"
112 113 114 115
            "\n"), progname);

    puts(_("This command is intended to be used by libvirtd "
           "and not used directly.\n"));
J
Jamie Strandboge 已提交
116 117 118 119 120 121
    return;
}

static void
vah_error(vahControl * ctl, int doexit, const char *str)
{
122
    fprintf(stderr, _("%s: error: %s%c"), progname, str, '\n');
J
Jamie Strandboge 已提交
123 124 125 126 127 128 129 130 131 132 133

    if (doexit) {
        if (ctl != NULL)
            vahDeinit(ctl);
        exit(EXIT_FAILURE);
    }
}

static void
vah_warning(const char *str)
{
134
    fprintf(stderr, _("%s: warning: %s%c"), progname, str, '\n');
J
Jamie Strandboge 已提交
135 136 137 138 139
}

static void
vah_info(const char *str)
{
140
    fprintf(stderr, _("%s:\n%s%c"), progname, str, '\n');
J
Jamie Strandboge 已提交
141 142 143 144 145 146 147 148
}

/*
 * run an apparmor_parser command
 */
static int
parserCommand(const char *profile_name, const char cmd)
{
149
    int result = -1;
J
Jamie Strandboge 已提交
150
    char flag[3];
151
    char *profile;
152 153
    int status;
    int ret;
J
Jamie Strandboge 已提交
154 155

    if (strchr("arR", cmd) == NULL) {
156
        vah_error(NULL, 0, _("invalid flag"));
J
Jamie Strandboge 已提交
157 158 159
        return -1;
    }

160
    g_snprintf(flag, 3, "-%c", cmd);
J
Jamie Strandboge 已提交
161

162
    profile = g_strdup_printf("%s/%s", APPARMOR_DIR "/libvirt", profile_name);
J
Jamie Strandboge 已提交
163 164

    if (!virFileExists(profile)) {
165
        vah_error(NULL, 0, _("profile does not exist"));
166
        goto cleanup;
J
Jamie Strandboge 已提交
167 168 169 170
    } else {
        const char * const argv[] = {
            "/sbin/apparmor_parser", flag, profile, NULL
        };
J
Ján Tomko 已提交
171
        g_autoptr(virCommand) command = virCommandNewArgs(argv);
172

173
        virCommandRawStatus(command);
174
        if ((ret = virCommandRun(command, &status)) != 0 ||
175 176
            (WIFEXITED(status) && WEXITSTATUS(status) != 0)) {
            if (ret != 0) {
177
                vah_error(NULL, 0, _("failed to run apparmor_parser"));
178
                goto cleanup;
179 180 181
            } else if (cmd == 'R' && WIFEXITED(status) &&
                       WEXITSTATUS(status) == 234) {
                vah_warning(_("unable to unload already unloaded profile"));
182
            } else {
183
                vah_error(NULL, 0, _("apparmor_parser exited with error"));
184
                goto cleanup;
185
            }
J
Jamie Strandboge 已提交
186 187 188
        }
    }

189 190
    result = 0;

191
 cleanup:
192 193 194
    VIR_FREE(profile);

    return result;
J
Jamie Strandboge 已提交
195 196 197 198 199 200
}

/*
 * Update the dynamic files
 */
static int
201 202
update_include_file(const char *include_file, const char *included_files,
                    bool append)
J
Jamie Strandboge 已提交
203 204
{
    int rc = -1;
205
    int plen, flen = 0;
J
Jamie Strandboge 已提交
206 207
    int fd;
    char *pcontent = NULL;
208
    char *existing = NULL;
J
Jamie Strandboge 已提交
209 210 211
    const char *warning =
         "# DO NOT EDIT THIS FILE DIRECTLY. IT IS MANAGED BY LIBVIRT.\n";

212 213 214 215 216 217
    if (virFileExists(include_file)) {
        flen = virFileReadAll(include_file, MAX_FILE_LEN, &existing);
        if (flen < 0)
            return rc;
    }

218 219 220 221
    if (append && virFileExists(include_file))
        pcontent = g_strdup_printf("%s%s", existing, included_files);
    else
        pcontent = g_strdup_printf("%s%s", warning, included_files);
J
Jamie Strandboge 已提交
222 223 224

    plen = strlen(pcontent);
    if (plen > MAX_FILE_LEN) {
225
        vah_error(NULL, 0, _("invalid length for new profile"));
226
        goto cleanup;
J
Jamie Strandboge 已提交
227 228 229
    }

    /* only update the disk profile if it is different */
230 231
    if (flen > 0 && flen == plen && STREQLEN(existing, pcontent, plen)) {
        rc = 0;
232
        goto cleanup;
J
Jamie Strandboge 已提交
233 234 235 236
    }

    /* write the file */
    if ((fd = open(include_file, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1) {
237
        vah_error(NULL, 0, _("failed to create include file"));
238
        goto cleanup;
J
Jamie Strandboge 已提交
239 240 241
    }

    if (safewrite(fd, pcontent, plen) < 0) { /* don't write the '\0' */
242
        VIR_FORCE_CLOSE(fd);
243
        vah_error(NULL, 0, _("failed to write to profile"));
244
        goto cleanup;
J
Jamie Strandboge 已提交
245 246
    }

247
    if (VIR_CLOSE(fd) != 0) {
248
        vah_error(NULL, 0, _("failed to close or write to profile"));
249
        goto cleanup;
J
Jamie Strandboge 已提交
250 251 252
    }
    rc = 0;

253
 cleanup:
J
Jamie Strandboge 已提交
254
    VIR_FREE(pcontent);
255
    VIR_FREE(existing);
J
Jamie Strandboge 已提交
256 257 258 259 260 261 262 263 264

    return rc;
}

/*
 * Create a profile based on a template
 */
static int
create_profile(const char *profile, const char *profile_name,
265
               const char *profile_files, int virtType)
J
Jamie Strandboge 已提交
266
{
267 268 269 270 271
    g_autofree char *template = NULL;
    g_autofree char *tcontent = NULL;
    g_autofree char *pcontent = NULL;
    g_autofree char *replace_name = NULL;
    g_autofree char *replace_files = NULL;
272
    char *tmp = NULL;
J
Jamie Strandboge 已提交
273 274 275 276
    const char *template_name = "\nprofile LIBVIRT_TEMPLATE";
    const char *template_end = "\n}";
    int tlen, plen;
    int fd;
277
    const char *driver_name = NULL;
J
Jamie Strandboge 已提交
278 279

    if (virFileExists(profile)) {
280
        vah_error(NULL, 0, _("profile exists"));
281
        return -1;
J
Jamie Strandboge 已提交
282 283
    }

284 285 286 287 288 289 290 291 292
    switch (virtType) {
    case VIR_DOMAIN_VIRT_QEMU:
    case VIR_DOMAIN_VIRT_KQEMU:
    case VIR_DOMAIN_VIRT_KVM:
        driver_name = "qemu";
        break;
    default:
        driver_name = virDomainVirtTypeToString(virtType);
    }
C
Cédric Bosdonnat 已提交
293

294
    template = g_strdup_printf("%s/TEMPLATE.%s", APPARMOR_DIR "/libvirt", driver_name);
J
Jamie Strandboge 已提交
295 296

    if (!virFileExists(template)) {
297
        vah_error(NULL, 0, _("template does not exist"));
298
        return -1;
J
Jamie Strandboge 已提交
299 300 301
    }

    if ((tlen = virFileReadAll(template, MAX_FILE_LEN, &tcontent)) < 0) {
302
        vah_error(NULL, 0, _("failed to read AppArmor template"));
303
        return -1;
J
Jamie Strandboge 已提交
304 305 306
    }

    if (strstr(tcontent, template_name) == NULL) {
307
        vah_error(NULL, 0, _("no replacement string in template"));
308
        return -1;
J
Jamie Strandboge 已提交
309 310 311
    }

    if (strstr(tcontent, template_end) == NULL) {
312
        vah_error(NULL, 0, _("no replacement string in template"));
313
        return -1;
J
Jamie Strandboge 已提交
314 315 316
    }

    /* '\nprofile <profile_name>\0' */
317
    replace_name = g_strdup_printf("\nprofile %s", profile_name);
J
Jamie Strandboge 已提交
318 319

    /* '\n<profile_files>\n}\0' */
320 321
    if (virtType != VIR_DOMAIN_VIRT_LXC)
    replace_files = g_strdup_printf("\n%s\n}", profile_files);
J
Jamie Strandboge 已提交
322

C
Cédric Bosdonnat 已提交
323
    plen = tlen + strlen(replace_name) - strlen(template_name) + 1;
324 325 326 327

    if (virtType != VIR_DOMAIN_VIRT_LXC)
        plen += strlen(replace_files) - strlen(template_end);

J
Jamie Strandboge 已提交
328
    if (plen > MAX_FILE_LEN || plen < tlen) {
329
        vah_error(NULL, 0, _("invalid length for new profile"));
330
        return -1;
J
Jamie Strandboge 已提交
331 332
    }

333
    if (!(pcontent = virStringReplace(tcontent, template_name, replace_name)))
334
        return -1;
J
Jamie Strandboge 已提交
335

336 337
    if (virtType != VIR_DOMAIN_VIRT_LXC) {
        if (!(tmp = virStringReplace(pcontent, template_end, replace_files)))
338
            return -1;
339
        VIR_FREE(pcontent);
340
        pcontent = g_steal_pointer(&tmp);
341
    }
J
Jamie Strandboge 已提交
342 343 344

    /* write the file */
    if ((fd = open(profile, O_CREAT | O_EXCL | O_WRONLY, 0644)) == -1) {
345
        vah_error(NULL, 0, _("failed to create profile"));
346
        return -1;
J
Jamie Strandboge 已提交
347 348 349
    }

    if (safewrite(fd, pcontent, plen - 1) < 0) { /* don't write the '\0' */
350
        VIR_FORCE_CLOSE(fd);
351
        vah_error(NULL, 0, _("failed to write to profile"));
352
        return -1;
J
Jamie Strandboge 已提交
353 354
    }

355
    if (VIR_CLOSE(fd) != 0) {
356
        vah_error(NULL, 0, _("failed to close or write to profile"));
357
        return -1;
J
Jamie Strandboge 已提交
358 359
    }

360
    return 0;
J
Jamie Strandboge 已提交
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
}

/*
 * Load an existing profile
 */
static int
parserLoad(const char *profile_name)
{
    return parserCommand(profile_name, 'a');
}

/*
 * Remove an existing profile
 */
static int
parserRemove(const char *profile_name)
{
    return parserCommand(profile_name, 'R');
}

/*
 * Replace an existing profile
 */
static int
parserReplace(const char *profile_name)
{
    return parserCommand(profile_name, 'r');
}

static int
valid_uuid(const char *uuid)
{
    unsigned char rawuuid[VIR_UUID_BUFLEN];

    if (strlen(uuid) != PROFILE_NAME_SIZE - 1)
        return -1;

    if (!STRPREFIX(uuid, AA_PREFIX))
        return -1;

    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 */
412
    const char *bad = "/[]{}?^,\"*";
J
Jamie Strandboge 已提交
413

414
    if (strlen(name) == 0)
J
Jamie Strandboge 已提交
415 416 417 418 419 420 421 422 423 424 425 426
        return -1;

    if (strcspn(name, bad) != strlen(name))
        return -1;

    return 0;
}

/* see if one of the strings in arr starts with str */
static int
array_starts_with(const char *str, const char * const *arr, const long size)
{
427
    size_t i;
J
Jamie Strandboge 已提交
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
    for (i = 0; i < size; i++) {
        if (strlen(str) < strlen(arr[i]))
            continue;

        if (STRPREFIX(str, arr[i]))
            return 0;
    }
    return 1;
}

/*
 * Don't allow access to special files or restricted paths such as /bin, /sbin,
 * /usr/bin, /usr/sbin and /etc. This is in an effort to prevent read/write
 * access to system files which could be used to elevate privileges. This is a
 * safety measure in case libvirtd is under a restrictive profile and is
 * subverted and trying to escape confinement.
 *
 * Note that we cannot exclude block devices because they are valid devices.
 * The TEMPLATE file can be adjusted to explicitly disallow these if needed.
 *
 * RETURN: -1 on error, 0 if ok, 1 if blocked
 */
static int
valid_path(const char *path, const bool readonly)
{
    const char * const restricted[] = {
        "/bin/",
        "/etc/",
        "/lib",
        "/lost+found/",
        "/proc/",
        "/sbin/",
        "/selinux/",
        "/sys/",
        "/usr/bin/",
        "/usr/lib",
        "/usr/sbin/",
        "/usr/share/",
        "/usr/local/bin/",
        "/usr/local/etc/",
        "/usr/local/lib",
        "/usr/local/sbin/"
    };
    /* these paths are ok for readonly, but not read/write */
    const char * const restricted_rw[] = {
        "/boot/",
        "/vmlinuz",
        "/initrd",
476
        "/initrd.img",
477
        "/usr/share/edk2/",
478
        "/usr/share/OVMF/",              /* for OVMF images */
479 480
        "/usr/share/ovmf/",              /* for OVMF images */
        "/usr/share/AAVMF/",             /* for AAVMF images */
481 482
        "/usr/share/qemu-efi/",          /* for AAVMF images */
        "/usr/share/qemu-efi-aarch64/"   /* for AAVMF images */
J
Jamie Strandboge 已提交
483
    };
484 485
    /* override the above with these */
    const char * const override[] = {
486 487 488
        "/sys/devices/pci",                /* for hostdev pci devices */
        "/sys/kernel/config/target/vhost", /* for hostdev vhost_scsi devices */
        "/etc/libvirt-sandbox/services/"   /* for virt-sandbox service config */
489
    };
J
Jamie Strandboge 已提交
490

491 492 493
    const int nropaths = G_N_ELEMENTS(restricted);
    const int nrwpaths = G_N_ELEMENTS(restricted_rw);
    const int nopaths = G_N_ELEMENTS(override);
494

495
    if (path == NULL) {
496
        vah_error(NULL, 0, _("bad pathname"));
J
Jamie Strandboge 已提交
497 498 499 500 501 502 503 504 505
        return -1;
    }

    /* 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;

J
Jamie Strandboge 已提交
506 507 508 509
    /* Require an absolute path */
    if (STRNEQLEN(path, "/", 1))
        return 1;

510
    if (!virFileExists(path))
511
        vah_warning(_("path does not exist, skipping file type checks"));
J
Jamie Strandboge 已提交
512

513
    /* overrides are always allowed */
514
    if (array_starts_with(path, override, nopaths) == 0)
515
        return 0;
J
Jamie Strandboge 已提交
516

517 518
    /* allow read only paths upfront */
    if (readonly) {
519
        if (array_starts_with(path, restricted_rw, nrwpaths) == 0)
520
            return 0;
J
Jamie Strandboge 已提交
521 522
    }

523
    /* disallow RW access to all paths in restricted and restriced_rw */
524 525
    if ((array_starts_with(path, restricted, nropaths) == 0 ||
         array_starts_with(path, restricted_rw, nrwpaths) == 0))
526 527
        return 1;

J
Jamie Strandboge 已提交
528 529 530
    return 0;
}

531 532 533 534 535 536
static int
verify_xpath_context(xmlXPathContextPtr ctxt)
{
    char *tmp = NULL;

    if (!ctxt) {
537
        vah_warning(_("Invalid context"));
538
        return -1;
539 540 541 542
    }

    /* check if have <name> */
    if (!(tmp = virXPathString("string(./name[1])", ctxt))) {
543
        vah_warning(_("Could not find <name>"));
544
        return -1;
545 546 547 548 549
    }
    VIR_FREE(tmp);

    /* check if have <uuid> */
    if (!(tmp = virXPathString("string(./uuid[1])", ctxt))) {
550
        vah_warning(_("Could not find <uuid>"));
551
        return -1;
552 553 554
    }
    VIR_FREE(tmp);

555
    return 0;
556 557
}

558 559
/*
 * Parse the xml we received to fill in the following:
560
 * ctl->virtType
561
 * ctl->os
562 563 564 565 566 567 568 569 570 571
 * ctl->arch
 *
 * These are suitable for setting up a virCapsPtr
 */
static int
caps_mockup(vahControl * ctl, const char *xmlStr)
{
    int rc = -1;
    xmlDocPtr xml = NULL;
    xmlXPathContextPtr ctxt = NULL;
572
    char *arch;
573

574
    if (!(xml = virXMLParseStringCtxt(xmlStr, _("(domain_definition)"),
575
                                      &ctxt))) {
576 577 578
        goto cleanup;
    }

579
    if (!virXMLNodeNameEqual(ctxt->node, "domain")) {
580
        vah_error(NULL, 0, _("unexpected root element, expecting <domain>"));
581 582 583
        goto cleanup;
    }

584 585 586 587
    /* Quick sanity check for some required elements */
    if (verify_xpath_context(ctxt) != 0)
        goto cleanup;

588 589 590 591 592
    ctl->virtType = virXPathString("string(./@type)", ctxt);
    if (!ctl->virtType) {
        vah_error(ctl, 0, _("domain type is not defined"));
        goto cleanup;
    }
593 594
    ctl->os = virXPathString("string(./os/type[1])", ctxt);
    if (!ctl->os) {
595
        vah_error(ctl, 0, _("os.type is not defined"));
596 597
        goto cleanup;
    }
598 599 600 601 602 603
    arch = virXPathString("string(./os/type[1]/@arch)", ctxt);
    if (!arch) {
        ctl->arch = virArchFromHost();
    } else {
        ctl->arch = virArchFromString(arch);
        VIR_FREE(arch);
604 605 606 607
    }

    rc = 0;

608
 cleanup:
609
    xmlFreeDoc(xml);
610 611 612 613 614
    xmlXPathFreeContext(ctxt);

    return rc;
}

615 616 617
virDomainDefParserConfig virAAHelperDomainDefParserConfig = {
    .features = VIR_DOMAIN_DEF_FEATURE_MEMORY_HOTPLUG |
                VIR_DOMAIN_DEF_FEATURE_OFFLINE_VCPUPIN |
618 619
                VIR_DOMAIN_DEF_FEATURE_INDIVIDUAL_VCPUS |
                VIR_DOMAIN_DEF_FEATURE_NET_MODEL_STRING,
620
};
621

J
Jamie Strandboge 已提交
622 623 624
static int
get_definition(vahControl * ctl, const char *xmlStr)
{
625
    int ostype, virtType;
J
Jamie Strandboge 已提交
626 627 628 629 630 631
    virCapsGuestPtr guest;  /* this is freed when caps is freed */

    /*
     * mock up some capabilities. We don't currently use these explicitly,
     * but need them for virDomainDefParseString().
     */
632
    if (caps_mockup(ctl, xmlStr) != 0)
633
        return -1;
J
Jamie Strandboge 已提交
634

635
    if ((ctl->caps = virCapabilitiesNew(ctl->arch, true, true)) == NULL) {
636
        vah_error(ctl, 0, _("could not allocate memory"));
637
        return -1;
J
Jamie Strandboge 已提交
638 639
    }

640 641
    if (!(ctl->xmlopt = virDomainXMLOptionNew(&virAAHelperDomainDefParserConfig,
                                              NULL, NULL, NULL, NULL))) {
642
        vah_error(ctl, 0, _("Failed to create XML config object"));
643
        return -1;
644 645
    }

646
    if ((ostype = virDomainOSTypeFromString(ctl->os)) < 0) {
647
        vah_error(ctl, 0, _("unknown OS type"));
648
        return -1;
649 650
    }

J
Jamie Strandboge 已提交
651
    if ((guest = virCapabilitiesAddGuest(ctl->caps,
652
                                         ostype,
653
                                         ctl->arch,
J
Jamie Strandboge 已提交
654 655 656 657
                                         NULL,
                                         NULL,
                                         0,
                                         NULL)) == NULL) {
658
        vah_error(ctl, 0, _("could not allocate memory"));
659
        return -1;
660 661 662 663
    }

    if ((virtType = virDomainVirtTypeFromString(ctl->virtType)) < 0) {
        vah_error(ctl, 0, _("unknown virtualization type"));
664
        return -1;
665 666 667 668 669 670 671 672 673
    }

    if (virCapabilitiesAddGuestDomain(guest,
                                      virtType,
                                      NULL,
                                      NULL,
                                      0,
                                      NULL) == NULL) {
        vah_error(ctl, 0, _("could not allocate memory"));
674
        return -1;
J
Jamie Strandboge 已提交
675 676
    }

677
    ctl->def = virDomainDefParseString(xmlStr,
678
                                       ctl->xmlopt, NULL,
679
                                       VIR_DOMAIN_DEF_PARSE_SKIP_SECLABEL |
680
                                       VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE);
681

J
Jamie Strandboge 已提交
682
    if (ctl->def == NULL) {
683
        vah_error(ctl, 0, _("could not parse XML"));
684
        return -1;
J
Jamie Strandboge 已提交
685 686 687
    }

    if (!ctl->def->name) {
688
        vah_error(ctl, 0, _("could not find name in XML"));
689
        return -1;
J
Jamie Strandboge 已提交
690 691 692
    }

    if (valid_name(ctl->def->name) != 0) {
693
        vah_error(ctl, 0, _("bad name"));
694
        return -1;
J
Jamie Strandboge 已提交
695 696
    }

697
    return 0;
J
Jamie Strandboge 已提交
698 699
}

700 701 702 703
/**
  * The permissions allowed are apparmor valid permissions and 'R'. 'R' stands for
  * read with no explicit deny rule.
  */
J
Jamie Strandboge 已提交
704
static int
F
Felix Geyer 已提交
705
vah_add_path(virBufferPtr buf, const char *path, const char *perms, bool recursive)
J
Jamie Strandboge 已提交
706 707 708 709
{
    char *tmp = NULL;
    int rc = -1;
    bool readonly = true;
710 711
    bool explicit_deny_rule = true;
    char *sub = NULL;
712
    char *perms_new = NULL;
713 714 715
    char *pathdir = NULL;
    char *pathtmp = NULL;
    char *pathreal = NULL;
J
Jamie Strandboge 已提交
716 717 718 719

    if (path == NULL)
        return rc;

J
Jamie Strandboge 已提交
720 721 722 723 724 725
    /* Skip files without an absolute path. Not having one confuses the
     * apparmor parser and this also ensures things like tcp consoles don't
     * get added to the profile.
     */
    if (STRNEQLEN(path, "/", 1)) {
        vah_warning(path);
726
        vah_warning(_("skipped non-absolute path"));
J
Jamie Strandboge 已提交
727 728 729
        return 0;
    }

730 731 732 733 734 735 736
    /* files might be created by qemu later on and not exist right now.
     * But realpath needs a valid path to work on, therefore:
     * 1. walk the path to find longest valid path
     * 2. get the realpath of that valid path
     * 3. re-combine the realpath with the remaining suffix
     * Note: A totally non existent path is used as-is
     */
737
     pathdir = g_strdup(path);
738
     while (!virFileExists(pathdir)) {
739
         pathtmp = g_path_get_dirname(pathdir);
740
         VIR_FREE(pathdir);
741
         pathdir = g_steal_pointer(&pathtmp);
742 743 744 745
     }

    if (strlen(pathdir) == 1) {
        /* nothing of the path does exist yet */
746
        tmp = g_strdup(path);
747
    } else {
748
        pathtmp = g_strdup(path + strlen(pathdir));
749 750 751 752
        if ((pathreal = realpath(pathdir, NULL)) == NULL) {
            vah_error(NULL, 0, pathdir);
            vah_error(NULL, 0, _("could not find realpath"));
            goto cleanup;
J
Jamie Strandboge 已提交
753
        }
754
        tmp = g_strdup_printf("%s%s", pathreal, pathtmp);
755
    }
J
Jamie Strandboge 已提交
756

757
    perms_new = g_strdup(perms);
758 759

    if (strchr(perms_new, 'w') != NULL) {
J
Jamie Strandboge 已提交
760
        readonly = false;
761 762 763
        explicit_deny_rule = false;
    }

764
    if ((sub = strchr(perms_new, 'R')) != NULL) {
765 766 767 768
        /* Don't write the invalid R permission, replace it with 'r' */
        sub[0] = 'r';
        explicit_deny_rule = false;
    }
J
Jamie Strandboge 已提交
769 770 771 772 773

    rc = valid_path(tmp, readonly);
    if (rc != 0) {
        if (rc > 0) {
            vah_error(NULL, 0, path);
774
            vah_error(NULL, 0, _("skipped restricted file"));
J
Jamie Strandboge 已提交
775
        }
776
        goto cleanup;
J
Jamie Strandboge 已提交
777 778
    }

779 780 781
    if (tmp[strlen(tmp) - 1] == '/')
        tmp[strlen(tmp) - 1] = '\0';

782 783
    virBufferAsprintf(buf, "  \"%s%s\" %s,\n", tmp, recursive ? "/**" : "",
                      perms_new);
784
    if (explicit_deny_rule) {
785
        virBufferAddLit(buf, "  # don't audit writes to readonly files\n");
F
Felix Geyer 已提交
786 787 788 789 790
        virBufferAsprintf(buf, "  deny \"%s%s\" w,\n", tmp, recursive ? "/**" : "");
    }
    if (recursive) {
        /* allow reading (but not creating) the dir */
        virBufferAsprintf(buf, "  \"%s/\" r,\n", tmp);
791
    }
J
Jamie Strandboge 已提交
792

793
 cleanup:
794 795 796
    VIR_FREE(pathdir);
    VIR_FREE(pathtmp);
    VIR_FREE(pathreal);
797
    VIR_FREE(perms_new);
798
    VIR_FREE(tmp);
J
Jamie Strandboge 已提交
799 800 801 802

    return rc;
}

F
Felix Geyer 已提交
803 804 805 806 807 808
static int
vah_add_file(virBufferPtr buf, const char *path, const char *perms)
{
    return vah_add_path(buf, path, perms, false);
}

809 810 811 812 813 814 815 816 817 818 819 820
static int
vah_add_file_chardev(virBufferPtr buf,
                     const char *path,
                     const char *perms,
                     const int type)
{
    char *pipe_in;
    char *pipe_out;
    int rc = -1;

    if (type == VIR_DOMAIN_CHR_TYPE_PIPE) {
        /* add the pipe input */
821
        pipe_in = g_strdup_printf("%s.in", path);
822 823 824 825 826

        if (vah_add_file(buf, pipe_in, perms) != 0)
            goto clean_pipe_in;

        /* add the pipe output */
827
        pipe_out = g_strdup_printf("%s.out", path);
828 829 830 831 832 833 834 835 836 837 838 839

        if (vah_add_file(buf, pipe_out, perms) != 0)
            goto clean_pipe_out;

        rc = 0;
      clean_pipe_out:
        VIR_FREE(pipe_out);
      clean_pipe_in:
        VIR_FREE(pipe_in);
    } else {
        /* add the file */
        if (vah_add_file(buf, path, perms) != 0)
840
            return -1;
841 842 843 844 845 846
        rc = 0;
    }

    return rc;
}

J
Jamie Strandboge 已提交
847
static int
J
Ján Tomko 已提交
848
file_iterate_hostdev_cb(virUSBDevicePtr dev G_GNUC_UNUSED,
849 850 851 852 853 854 855
                        const char *file, void *opaque)
{
    virBufferPtr buf = opaque;
    return vah_add_file(buf, file, "rw");
}

static int
J
Ján Tomko 已提交
856
file_iterate_pci_cb(virPCIDevicePtr dev G_GNUC_UNUSED,
857
                    const char *file, void *opaque)
J
Jamie Strandboge 已提交
858 859 860 861 862
{
    virBufferPtr buf = opaque;
    return vah_add_file(buf, file, "rw");
}

863
static int
864
add_file_path(virStorageSourcePtr src,
865
              size_t depth,
866
              virBufferPtr buf)
867 868 869
{
    int ret;

870 871 872 873
    /* execute the callback only for local storage */
    if (!src->path || !virStorageSourceIsLocalStorage(src))
        return 0;

874
    if (depth == 0) {
875 876
        if (src->readonly)
            ret = vah_add_file(buf, src->path, "rk");
877
        else
878
            ret = vah_add_file(buf, src->path, "rwk");
879
    } else {
880
        ret = vah_add_file(buf, src->path, "rk");
881 882
    }

883 884 885
    if (ret != 0)
        ret = -1;

886 887 888
    return ret;
}

889 890

static int
891
storage_source_add_files(virStorageSourcePtr src,
892 893
                         virBufferPtr buf,
                         size_t depth)
894 895 896
{
    virStorageSourcePtr tmp;

897
    for (tmp = src; virStorageSourceIsBacking(tmp); tmp = tmp->backingStore) {
898 899
        if (add_file_path(tmp, depth, buf) < 0)
            return -1;
900 901 902 903 904 905 906

        depth++;
    }

    return 0;
}

J
Jamie Strandboge 已提交
907 908 909 910 911
static int
get_files(vahControl * ctl)
{
    virBuffer buf = VIR_BUFFER_INITIALIZER;
    int rc = -1;
912
    size_t i;
J
Jamie Strandboge 已提交
913
    char *uuid;
914
    char *mem_path = NULL;
J
Jamie Strandboge 已提交
915
    char uuidstr[VIR_UUID_STRING_BUFLEN];
916
    bool needsVfio = false, needsvhost = false, needsgl = false;
J
Jamie Strandboge 已提交
917 918 919

    /* verify uuid is same as what we were given on the command line */
    virUUIDFormat(ctl->def->uuid, uuidstr);
920
    uuid = g_strdup_printf("%s%s", AA_PREFIX, uuidstr);
J
Jamie Strandboge 已提交
921 922

    if (STRNEQ(uuid, ctl->uuid)) {
923
        vah_error(ctl, 0, _("given uuid does not match XML uuid"));
924
        goto cleanup;
J
Jamie Strandboge 已提交
925 926
    }

927 928
    /* load the storage driver so that backing store can be accessed */
#ifdef WITH_STORAGE
929
    virDriverLoadModule("storage", "storageRegister", false);
930
#endif
931

932
    for (i = 0; i < ctl->def->ndisks; i++) {
933 934
        virDomainDiskDefPtr disk = ctl->def->disks[i];

935
        if (!virDomainDiskGetSource(disk))
E
Eric Blake 已提交
936
            continue;
937 938 939
        /* XXX - if we knew the qemu user:group here we could send it in
         *        so that the open could be re-tried as that user:group.
         */
940
        if (!virStorageSourceHasBacking(disk->src))
941
            virStorageFileGetMetadata(disk->src, -1, -1, false);
942

943
         /* XXX should handle open errors more careful than just ignoring them.
944
         */
945
        if (storage_source_add_files(disk->src, &buf, 0) < 0)
946
            goto cleanup;
947
    }
J
Jamie Strandboge 已提交
948 949

    for (i = 0; i < ctl->def->nserials; i++)
950
        if (ctl->def->serials[i] &&
951 952 953 954 955 956 957
            (ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_PTY ||
             ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_DEV ||
             ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_FILE ||
             ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_UNIX ||
             ctl->def->serials[i]->source->type == VIR_DOMAIN_CHR_TYPE_PIPE) &&
            ctl->def->serials[i]->source->data.file.path &&
            ctl->def->serials[i]->source->data.file.path[0] != '\0')
958
            if (vah_add_file_chardev(&buf,
959
                                     ctl->def->serials[i]->source->data.file.path,
960
                                     "rw",
961
                                     ctl->def->serials[i]->source->type) != 0)
962
                goto cleanup;
J
Jamie Strandboge 已提交
963

964 965
    for (i = 0; i < ctl->def->nconsoles; i++)
        if (ctl->def->consoles[i] &&
966 967 968 969 970 971 972
            (ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_PTY ||
             ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_DEV ||
             ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_FILE ||
             ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_UNIX ||
             ctl->def->consoles[i]->source->type == VIR_DOMAIN_CHR_TYPE_PIPE) &&
            ctl->def->consoles[i]->source->data.file.path &&
            ctl->def->consoles[i]->source->data.file.path[0] != '\0')
973
            if (vah_add_file(&buf,
974
                             ctl->def->consoles[i]->source->data.file.path, "rw") != 0)
975
                goto cleanup;
J
Jamie Strandboge 已提交
976

977
    for (i = 0; i < ctl->def->nparallels; i++)
978
        if (ctl->def->parallels[i] &&
979 980 981 982 983 984 985
            (ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_PTY ||
             ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_DEV ||
             ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_FILE ||
             ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_UNIX ||
             ctl->def->parallels[i]->source->type == VIR_DOMAIN_CHR_TYPE_PIPE) &&
            ctl->def->parallels[i]->source->data.file.path &&
            ctl->def->parallels[i]->source->data.file.path[0] != '\0')
986
            if (vah_add_file_chardev(&buf,
987
                                     ctl->def->parallels[i]->source->data.file.path,
988
                                     "rw",
989
                                     ctl->def->parallels[i]->source->type) != 0)
990
                goto cleanup;
991

992
    for (i = 0; i < ctl->def->nchannels; i++)
993
        if (ctl->def->channels[i] &&
994 995 996 997 998 999 1000
            (ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_PTY ||
             ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_DEV ||
             ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_FILE ||
             ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_UNIX ||
             ctl->def->channels[i]->source->type == VIR_DOMAIN_CHR_TYPE_PIPE) &&
            ctl->def->channels[i]->source->data.file.path &&
            ctl->def->channels[i]->source->data.file.path[0] != '\0')
1001
            if (vah_add_file_chardev(&buf,
1002
                                     ctl->def->channels[i]->source->data.file.path,
1003
                                     "rw",
1004
                                     ctl->def->channels[i]->source->type) != 0)
1005
                goto cleanup;
1006

1007
    if (ctl->def->os.kernel)
J
Jamie Strandboge 已提交
1008
        if (vah_add_file(&buf, ctl->def->os.kernel, "r") != 0)
1009
            goto cleanup;
J
Jamie Strandboge 已提交
1010

1011
    if (ctl->def->os.initrd)
J
Jamie Strandboge 已提交
1012
        if (vah_add_file(&buf, ctl->def->os.initrd, "r") != 0)
1013
            goto cleanup;
O
Olivia Yin 已提交
1014 1015 1016

    if (ctl->def->os.dtb)
        if (vah_add_file(&buf, ctl->def->os.dtb, "r") != 0)
1017
            goto cleanup;
J
Jamie Strandboge 已提交
1018

J
Ján Tomko 已提交
1019 1020 1021 1022
    if (ctl->def->os.slic_table)
        if (vah_add_file(&buf, ctl->def->os.slic_table, "r") != 0)
            goto cleanup;

1023
    if (ctl->def->os.loader && ctl->def->os.loader->path)
1024
        if (vah_add_file(&buf, ctl->def->os.loader->path, "rk") != 0)
1025
            goto cleanup;
1026

1027
    if (ctl->def->os.loader && ctl->def->os.loader->nvram)
1028
        if (vah_add_file(&buf, ctl->def->os.loader->nvram, "rwk") != 0)
1029 1030
            goto cleanup;

1031
    for (i = 0; i < ctl->def->ngraphics; i++) {
1032 1033
        virDomainGraphicsDefPtr graphics = ctl->def->graphics[i];
        size_t n;
1034 1035 1036 1037
        const char *rendernode = virDomainGraphicsGetRenderNode(graphics);

        if (rendernode) {
            vah_add_file(&buf, rendernode, "rw");
1038
            needsgl = true;
1039 1040 1041
        } else {
            if (virDomainGraphicsNeedsAutoRenderNode(graphics)) {
                char *defaultRenderNode = virHostGetDRMRenderNode();
1042
                needsgl = true;
1043 1044 1045 1046 1047 1048 1049

                if (defaultRenderNode) {
                    vah_add_file(&buf, defaultRenderNode, "rw");
                    VIR_FREE(defaultRenderNode);
                }
            }
        }
1050 1051 1052 1053 1054 1055 1056 1057 1058

        for (n = 0; n < graphics->nListens; n++) {
            virDomainGraphicsListenDef listenObj = graphics->listens[n];

            if (listenObj.type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET &&
                listenObj.socket &&
                vah_add_file(&buf, listenObj.socket, "rw"))
                goto cleanup;
        }
1059 1060
    }

1061 1062 1063 1064
    if (ctl->def->ngraphics == 1 &&
        ctl->def->graphics[0]->type == VIR_DOMAIN_GRAPHICS_TYPE_SDL)
        if (vah_add_file(&buf, ctl->def->graphics[0]->data.sdl.xauth,
                         "r") != 0)
1065
            goto cleanup;
J
Jamie Strandboge 已提交
1066 1067 1068 1069

    for (i = 0; i < ctl->def->nhostdevs; i++)
        if (ctl->def->hostdevs[i]) {
            virDomainHostdevDefPtr dev = ctl->def->hostdevs[i];
1070
            virDomainHostdevSubsysUSBPtr usbsrc = &dev->source.subsys.u.usb;
J
Jamie Strandboge 已提交
1071 1072
            switch (dev->source.subsys.type) {
            case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: {
1073
                virUSBDevicePtr usb =
1074
                    virUSBDeviceNew(usbsrc->bus, usbsrc->device, NULL);
1075 1076 1077 1078

                if (usb == NULL)
                    continue;

1079 1080 1081
                if (virHostdevFindUSBDevice(dev, true, &usb) < 0)
                    continue;

1082 1083
                rc = virUSBDeviceFileIterate(usb, file_iterate_hostdev_cb, &buf);
                virUSBDeviceFree(usb);
1084
                if (rc != 0)
1085
                    goto cleanup;
J
Jamie Strandboge 已提交
1086 1087
                break;
            }
1088

1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
            case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: {
                virDomainHostdevSubsysMediatedDevPtr mdevsrc = &dev->source.subsys.u.mdev;
                switch ((virMediatedDeviceModelType) mdevsrc->model) {
                    case VIR_MDEV_MODEL_TYPE_VFIO_PCI:
                    case VIR_MDEV_MODEL_TYPE_VFIO_AP:
                    case VIR_MDEV_MODEL_TYPE_VFIO_CCW:
                        needsVfio = true;
                        break;
                    case VIR_MDEV_MODEL_TYPE_LAST:
                    default:
                        virReportEnumRangeError(virMediatedDeviceModelType,
                                                mdevsrc->model);
                        break;
                }
                break;
            }

J
Jamie Strandboge 已提交
1106
            case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: {
1107
                virPCIDevicePtr pci = virPCIDeviceNew(
1108 1109 1110 1111
                           dev->source.subsys.u.pci.addr.domain,
                           dev->source.subsys.u.pci.addr.bus,
                           dev->source.subsys.u.pci.addr.slot,
                           dev->source.subsys.u.pci.addr.function);
J
Jamie Strandboge 已提交
1112

1113
                virDomainHostdevSubsysPCIBackendType backend = dev->source.subsys.u.pci.backend;
1114 1115 1116 1117 1118
                if (backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO ||
                        backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT) {
                    needsVfio = true;
                }

J
Jamie Strandboge 已提交
1119 1120 1121
                if (pci == NULL)
                    continue;

1122 1123
                rc = virPCIDeviceFileIterate(pci, file_iterate_pci_cb, &buf);
                virPCIDeviceFree(pci);
J
Jamie Strandboge 已提交
1124 1125 1126

                break;
            }
1127

J
Jamie Strandboge 已提交
1128 1129 1130 1131 1132 1133
            default:
                rc = 0;
                break;
            } /* switch */
        }

F
Felix Geyer 已提交
1134 1135 1136 1137 1138
    for (i = 0; i < ctl->def->nfss; i++) {
        if (ctl->def->fss[i] &&
                ctl->def->fss[i]->type == VIR_DOMAIN_FS_TYPE_MOUNT &&
                (ctl->def->fss[i]->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_PATH ||
                 ctl->def->fss[i]->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_DEFAULT) &&
1139
                ctl->def->fss[i]->src) {
F
Felix Geyer 已提交
1140 1141
            virDomainFSDefPtr fs = ctl->def->fss[i];

1142 1143 1144
            /* We don't need to add deny rw rules for readonly mounts,
             * this can only lead to troubles when mounting / readonly.
             */
1145
            if (vah_add_path(&buf, fs->src->path, fs->readonly ? "R" : "rw", true) != 0)
F
Felix Geyer 已提交
1146 1147 1148 1149
                goto cleanup;
        }
    }

1150 1151 1152 1153 1154 1155 1156 1157
    for (i = 0; i < ctl->def->ninputs; i++) {
        if (ctl->def->inputs[i] &&
                ctl->def->inputs[i]->type == VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH) {
            if (vah_add_file(&buf, ctl->def->inputs[i]->source.evdev, "rw") != 0)
                goto cleanup;
        }
    }

1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169
    for (i = 0; i < ctl->def->nnets; i++) {
        if (ctl->def->nets[i] &&
                ctl->def->nets[i]->type == VIR_DOMAIN_NET_TYPE_VHOSTUSER &&
                ctl->def->nets[i]->data.vhostuser) {
            virDomainChrSourceDefPtr vhu = ctl->def->nets[i]->data.vhostuser;

            if (vah_add_file_chardev(&buf, vhu->data.nix.path, "rw",
                       vhu->type) != 0)
                goto cleanup;
        }
    }

1170 1171 1172 1173 1174 1175 1176 1177
    for (i = 0; i < ctl->def->nmems; i++) {
        if (ctl->def->mems[i] &&
                ctl->def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM) {
            if (vah_add_file(&buf, ctl->def->mems[i]->nvdimmPath, "rw") != 0)
                goto cleanup;
        }
    }

1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189
    for (i = 0; i < ctl->def->nsysinfo; i++) {
        size_t j;

        for (j = 0; j < ctl->def->sysinfo[i]->nfw_cfgs; j++) {
            virSysinfoFWCfgDefPtr f = &ctl->def->sysinfo[i]->fw_cfgs[j];

            if (f->file &&
                vah_add_file(&buf, f->file, "r") != 0)
                goto cleanup;
        }
    }

1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220
    for (i = 0; i < ctl->def->nshmems; i++) {
        virDomainShmemDef *shmem = ctl->def->shmems[i];
        /* explicit server paths can be on any model to overwrites defaults.
         * When the server path is enabled, use it - otherwise fallback to
         * model dependent defaults. */
        if (shmem->server.enabled &&
            shmem->server.chr.data.nix.path) {
                if (vah_add_file(&buf, shmem->server.chr.data.nix.path,
                        "rw") != 0)
                    goto cleanup;
        } else {
            switch (shmem->model) {
            case VIR_DOMAIN_SHMEM_MODEL_IVSHMEM_PLAIN:
                /* until exposed, recreate qemuBuildShmemBackendMemProps */
                mem_path = g_strdup_printf("/dev/shm/%s", shmem->name);
                break;
            case VIR_DOMAIN_SHMEM_MODEL_IVSHMEM_DOORBELL:
            case VIR_DOMAIN_SHMEM_MODEL_IVSHMEM:
                 /* until exposed, recreate qemuDomainPrepareShmemChardev */
                mem_path = g_strdup_printf("/var/lib/libvirt/shmem-%s-sock",
                               shmem->name);
                break;
            }
            if (mem_path != NULL) {
                if (vah_add_file(&buf, mem_path, "rw") != 0)
                    goto cleanup;
            }
        }
    }


1221 1222 1223 1224
    if (ctl->def->tpm) {
        char *shortName = NULL;
        const char *tpmpath = NULL;

1225
        if (ctl->def->tpm->type == VIR_DOMAIN_TPM_TYPE_EMULATOR) {
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241
            shortName = virDomainDefGetShortName(ctl->def);

            switch (ctl->def->tpm->version) {
            case VIR_DOMAIN_TPM_VERSION_1_2:
                tpmpath = "tpm1.2";
                break;
            case VIR_DOMAIN_TPM_VERSION_2_0:
                tpmpath = "tpm2";
                break;
            case VIR_DOMAIN_TPM_VERSION_DEFAULT:
            case VIR_DOMAIN_TPM_VERSION_LAST:
                break;
            }

            /* Unix socket for QEMU and swtpm to use */
            virBufferAsprintf(&buf,
1242 1243
                "  \"%s/libvirt/qemu/swtpm/%s-swtpm.sock\" rw,\n",
                RUNSTATEDIR, shortName);
1244 1245 1246 1247
            /* Paths for swtpm to use: give it access to its state
             * directory, log, and PID files.
             */
            virBufferAsprintf(&buf,
1248
                "  \"%s/lib/libvirt/swtpm/%s/%s/**\" rwk,\n",
1249 1250
                LOCALSTATEDIR, uuidstr, tpmpath);
            virBufferAsprintf(&buf,
1251
                "  \"%s/log/swtpm/libvirt/qemu/%s-swtpm.log\" w,\n",
1252 1253
                LOCALSTATEDIR, ctl->def->name);
            virBufferAsprintf(&buf,
1254 1255
                "  \"%s/libvirt/qemu/swtpm/%s-swtpm.pid\" rw,\n",
                RUNSTATEDIR, shortName);
1256 1257 1258 1259 1260

            VIR_FREE(shortName);
        }
    }

1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293
    for (i = 0; i < ctl->def->nsmartcards; i++) {
        virDomainSmartcardDefPtr sc = ctl->def->smartcards[i];
        virDomainSmartcardType sc_type = sc->type;
        char *sc_db = (char *)VIR_DOMAIN_SMARTCARD_DEFAULT_DATABASE;
        if (sc->data.cert.database)
            sc_db = sc->data.cert.database;
        switch (sc_type) {
            /*
             * Note: At time of writing, to get this working, qemu seccomp sandbox has
             * to be disabled or the host must be running QEMU with commit
             * 9a1565a03b79d80b236bc7cc2dbce52a2ef3a1b8.
             * It's possibly due to libcacard:vcard_emul_new_event_thread(), which calls
             * PR_CreateThread(), which calls {g,s}etpriority(). And resourcecontrol seccomp
             * filter forbids it (cf src/qemu/qemu_command.c which seems to always use
             * resourcecontrol=deny).
             */
            case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
                virBufferAddLit(&buf, "  \"/etc/pki/nssdb/{,*}\" rk,\n");
                break;
            case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
                virBufferAsprintf(&buf, "  \"%s/{,*}\" rk,\n", sc_db);
                break;
            /*
             * Nothing to do for passthrough, as the smartcard
             * access is done through TCP or Spice
             */
            case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
                break;
            case VIR_DOMAIN_SMARTCARD_TYPE_LAST:
                break;
        }
    }

1294 1295 1296
    if (ctl->def->virtType == VIR_DOMAIN_VIRT_KVM) {
        for (i = 0; i < ctl->def->nnets; i++) {
            virDomainNetDefPtr net = ctl->def->nets[i];
1297
            if (net && virDomainNetGetModelString(net)) {
1298 1299
                if (net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_QEMU)
                    continue;
1300
                if (!virDomainNetIsVirtioModel(net))
1301 1302 1303 1304 1305 1306
                    continue;
            }
            needsvhost = true;
        }
    }
    if (needsvhost)
1307
        virBufferAddLit(&buf, "  \"/dev/vhost-net\" rw,\n");
1308

1309
    if (needsVfio) {
1310 1311
        virBufferAddLit(&buf, "  \"/dev/vfio/vfio\" rw,\n");
        virBufferAddLit(&buf, "  \"/dev/vfio/[0-9]*\" rw,\n");
1312
    }
1313 1314 1315 1316 1317 1318 1319 1320 1321 1322
    if (needsgl) {
        /* if using gl all sorts of further dri related paths will be needed */
        virBufferAddLit(&buf, "  # DRI/Mesa/(e)GL config and driver paths\n");
        virBufferAddLit(&buf, "  \"/usr/lib{,32,64}/dri/*.so*\" mr,\n");
        virBufferAddLit(&buf, "  \"/usr/lib/@{multiarch}/dri/*.so*\" mr,\n");
        virBufferAddLit(&buf, "  \"/usr/lib/fglrx/dri/*.so*\" mr,\n");
        virBufferAddLit(&buf, "  \"/etc/drirc\" r,\n");
        virBufferAddLit(&buf, "  \"/usr/share/drirc.d/{,*.conf}\" r,\n");
        virBufferAddLit(&buf, "  \"/etc/glvnd/egl_vendor.d/{,*}\" r,\n");
        virBufferAddLit(&buf, "  \"/usr/share/glvnd/egl_vendor.d/{,*}\" r,\n");
1323 1324 1325 1326 1327
        virBufferAddLit(&buf, "  \"/usr/share/egl/egl_external_platform.d/\" r,\n");
        virBufferAddLit(&buf, "  \"/usr/share/egl/egl_external_platform.d/*\" r,\n");
        virBufferAddLit(&buf, "  \"/proc/modules\" r,\n");
        virBufferAddLit(&buf, "  \"/proc/driver/nvidia/params\" r,\n");
        virBufferAddLit(&buf, "  \"/dev/nvidiactl\" rw,\n");
1328 1329
        virBufferAddLit(&buf, "  # Probe DRI device attributes\n");
        virBufferAddLit(&buf, "  \"/dev/dri/\" r,\n");
1330
        virBufferAddLit(&buf, "  \"/sys/devices/**/{uevent,vendor,device,subsystem_vendor,subsystem_device}\" r,\n");
1331 1332 1333
        virBufferAddLit(&buf, "  # dri libs will trigger that, but t is not requited and DAC would deny it anyway\n");
        virBufferAddLit(&buf, "  deny \"/var/lib/libvirt/.cache/\" w,\n");
    }
1334

1335
    if (ctl->newfile)
1336
        if (vah_add_file(&buf, ctl->newfile, "rwk") != 0)
1337
            goto cleanup;
J
Jamie Strandboge 已提交
1338 1339 1340 1341

    rc = 0;
    ctl->files = virBufferContentAndReset(&buf);

1342
 cleanup:
1343
    VIR_FREE(mem_path);
J
Jamie Strandboge 已提交
1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357
    VIR_FREE(uuid);
    return rc;
}

static int
vahParseArgv(vahControl * ctl, int argc, char **argv)
{
    int arg, idx = 0;
    struct option opt[] = {
        {"add", 0, 0, 'a'},
        {"create", 0, 0, 'c'},
        {"dryrun", 0, 0, 'd'},
        {"delete", 0, 0, 'D'},
        {"add-file", 0, 0, 'f'},
1358
        {"append-file", 0, 0, 'F'},
J
Jamie Strandboge 已提交
1359 1360 1361 1362 1363 1364 1365
        {"help", 0, 0, 'h'},
        {"replace", 0, 0, 'r'},
        {"remove", 0, 0, 'R'},
        {"uuid", 1, 0, 'u'},
        {0, 0, 0, 0}
    };

1366
    while ((arg = getopt_long(argc, argv, "acdDhrRH:b:u:p:f:F:", opt,
J
Jamie Strandboge 已提交
1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381
            &idx)) != -1) {
        switch (arg) {
            case 'a':
                ctl->cmd = 'a';
                break;
            case 'c':
                ctl->cmd = 'c';
                break;
            case 'd':
                ctl->dryrun = true;
                break;
            case 'D':
                ctl->cmd = 'D';
                break;
            case 'f':
1382
            case 'F':
1383
                ctl->newfile = g_strdup(optarg);
1384
                ctl->append = arg == 'F';
J
Jamie Strandboge 已提交
1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397
                break;
            case 'h':
                vah_usage();
                exit(EXIT_SUCCESS);
                break;
            case 'r':
                ctl->cmd = 'r';
                break;
            case 'R':
                ctl->cmd = 'R';
                break;
            case 'u':
                if (strlen(optarg) > PROFILE_NAME_SIZE - 1)
1398
                    vah_error(ctl, 1, _("invalid UUID"));
1399
                if (virStrcpy((char *)ctl->uuid, optarg,
1400
                    PROFILE_NAME_SIZE) < 0)
1401
                    vah_error(ctl, 1, _("error copying UUID"));
J
Jamie Strandboge 已提交
1402 1403
                break;
            default:
1404
                vah_error(ctl, 1, _("unsupported option"));
J
Jamie Strandboge 已提交
1405 1406 1407 1408
                break;
        }
    }
    if (strchr("acDrR", ctl->cmd) == NULL)
1409
        vah_error(ctl, 1, _("bad command"));
J
Jamie Strandboge 已提交
1410 1411

    if (valid_uuid(ctl->uuid) != 0)
1412
        vah_error(ctl, 1, _("invalid UUID"));
J
Jamie Strandboge 已提交
1413 1414 1415 1416 1417 1418 1419 1420 1421

    if (!ctl->cmd) {
        vah_usage();
        exit(EXIT_FAILURE);
    }

    if (ctl->cmd == 'c' || ctl->cmd == 'r') {
        char *xmlStr = NULL;
        if (virFileReadLimFD(STDIN_FILENO, MAX_FILE_LEN, &xmlStr) < 0)
1422
            vah_error(ctl, 1, _("could not read xml file"));
J
Jamie Strandboge 已提交
1423 1424 1425

        if (get_definition(ctl, xmlStr) != 0 || ctl->def == NULL) {
            VIR_FREE(xmlStr);
1426
            vah_error(ctl, 1, _("could not get VM definition"));
J
Jamie Strandboge 已提交
1427 1428 1429 1430
        }
        VIR_FREE(xmlStr);

        if (get_files(ctl) != 0)
1431
            vah_error(ctl, 1, _("invalid VM definition"));
J
Jamie Strandboge 已提交
1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449
    }
    return 0;
}


/*
 * virt-aa-helper -c -u UUID < file.xml
 * virt-aa-helper -r -u UUID [-f <file>] < file.xml
 * virt-aa-helper -a -u UUID
 * virt-aa-helper -R -u UUID
 * virt-aa-helper -D -u UUID
 */
int
main(int argc, char **argv)
{
    vahControl _ctl, *ctl = &_ctl;
    virBuffer buf = VIR_BUFFER_INITIALIZER;
    int rc = -1;
1450 1451
    char *profile = NULL;
    char *include_file = NULL;
J
Jamie Strandboge 已提交
1452

1453
    if (virGettextInitialize() < 0 ||
1454 1455 1456 1457 1458
        virErrorInitialize() < 0) {
        fprintf(stderr, _("%s: initialization failed\n"), argv[0]);
        exit(EXIT_FAILURE);
    }

1459
    virFileActivateDirOverrideForProg(argv[0]);
1460

1461 1462 1463
    /* Initialize the log system */
    virLogSetFromEnv();

J
Jamie Strandboge 已提交
1464 1465
    /* clear the environment */
    environ = NULL;
1466
    if (g_setenv("PATH", "/sbin:/usr/sbin", TRUE) == FALSE)
1467 1468
        vah_error(ctl, 1, _("could not set PATH"));

1469
    /* ensure the traditional IFS setting */
1470
    if (g_setenv("IFS", " \t\n", TRUE) == FALSE)
1471
        vah_error(ctl, 1, _("could not set IFS"));
J
Jamie Strandboge 已提交
1472 1473 1474 1475 1476 1477 1478 1479 1480

    if (!(progname = strrchr(argv[0], '/')))
        progname = argv[0];
    else
        progname++;

    memset(ctl, 0, sizeof(vahControl));

    if (vahParseArgv(ctl, argc, argv) != 0)
1481
        vah_error(ctl, 1, _("could not parse arguments"));
J
Jamie Strandboge 已提交
1482

1483 1484
    profile = g_strdup_printf("%s/%s", APPARMOR_DIR "/libvirt", ctl->uuid);
    include_file = g_strdup_printf("%s/%s.files", APPARMOR_DIR "/libvirt", ctl->uuid);
J
Jamie Strandboge 已提交
1485

1486
    if (ctl->cmd == 'a') {
J
Jamie Strandboge 已提交
1487
        rc = parserLoad(ctl->uuid);
1488
    } else if (ctl->cmd == 'R' || ctl->cmd == 'D') {
J
Jamie Strandboge 已提交
1489 1490 1491 1492 1493 1494 1495 1496
        rc = parserRemove(ctl->uuid);
        if (ctl->cmd == 'D') {
            unlink(include_file);
            unlink(profile);
        }
    } else if (ctl->cmd == 'c' || ctl->cmd == 'r') {
        char *included_files = NULL;

1497
        if (ctl->cmd == 'c' && virFileExists(profile))
1498
            vah_error(ctl, 1, _("profile exists"));
J
Jamie Strandboge 已提交
1499

1500
        if (ctl->append && ctl->newfile) {
1501
            if (vah_add_file(&buf, ctl->newfile, "rwk") != 0)
1502
                goto cleanup;
1503
        } else {
1504 1505 1506
            if (ctl->def->virtType == VIR_DOMAIN_VIRT_QEMU ||
                ctl->def->virtType == VIR_DOMAIN_VIRT_KQEMU ||
                ctl->def->virtType == VIR_DOMAIN_VIRT_KVM) {
1507 1508
                virBufferAsprintf(&buf, "  \"%s/log/libvirt/**/%s.log\" w,\n",
                                  LOCALSTATEDIR, ctl->def->name);
1509
                virBufferAsprintf(&buf, "  \"%s/lib/libvirt/qemu/domain-%s/monitor.sock\" rw,\n",
1510
                                  LOCALSTATEDIR, ctl->def->name);
1511 1512
                virBufferAsprintf(&buf, "  \"%s/lib/libvirt/qemu/domain-%d-%.*s/*\" rw,\n",
                                  LOCALSTATEDIR, ctl->def->id, 20, ctl->def->name);
1513 1514 1515 1516
                virBufferAsprintf(&buf, "  \"%s/libvirt/**/%s.pid\" rwk,\n",
                                  RUNSTATEDIR, ctl->def->name);
                virBufferAsprintf(&buf, "  \"%s/libvirt/**/*.tunnelmigrate.dest.%s\" rw,\n",
                                  RUNSTATEDIR, ctl->def->name);
1517
            }
1518
            if (ctl->files)
1519
                virBufferAdd(&buf, ctl->files, -1);
1520
        }
J
Jamie Strandboge 已提交
1521 1522 1523 1524 1525 1526 1527 1528

        included_files = virBufferContentAndReset(&buf);

        /* (re)create the include file using included_files */
        if (ctl->dryrun) {
            vah_info(include_file);
            vah_info(included_files);
            rc = 0;
1529 1530 1531
        } else if (ctl->def->virtType == VIR_DOMAIN_VIRT_LXC) {
            rc = 0;
        } else if ((rc = update_include_file(include_file,
1532
                                             included_files,
1533
                                             ctl->append)) != 0) {
1534
            goto cleanup;
1535
        }
J
Jamie Strandboge 已提交
1536 1537 1538 1539 1540


        /* create the profile from TEMPLATE */
        if (ctl->cmd == 'c') {
            char *tmp = NULL;
1541
            tmp = g_strdup_printf("  #include <libvirt/%s.files>\n", ctl->uuid);
J
Jamie Strandboge 已提交
1542 1543 1544 1545 1546 1547

            if (ctl->dryrun) {
                vah_info(profile);
                vah_info(ctl->uuid);
                vah_info(tmp);
                rc = 0;
1548 1549
            } else if ((rc = create_profile(profile, ctl->uuid, tmp,
                                            ctl->def->virtType)) != 0) {
1550
                vah_error(ctl, 0, _("could not create profile"));
J
Jamie Strandboge 已提交
1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568
                unlink(include_file);
            }
            VIR_FREE(tmp);
        }

        if (rc == 0 && !ctl->dryrun) {
            if (ctl->cmd == 'c')
                rc = parserLoad(ctl->uuid);
            else
                rc = parserReplace(ctl->uuid);

            /* cleanup */
            if (rc != 0) {
                unlink(include_file);
                if (ctl->cmd == 'c')
                    unlink(profile);
            }
        }
1569
      cleanup:
J
Jamie Strandboge 已提交
1570 1571 1572 1573
        VIR_FREE(included_files);
    }

    vahDeinit(ctl);
1574 1575 1576 1577

    VIR_FREE(profile);
    VIR_FREE(include_file);

J
Jamie Strandboge 已提交
1578 1579
    exit(rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}