virt-aa-helper.c 33.7 KB
Newer Older
J
Jamie Strandboge 已提交
1 2 3

/*
 * virt-aa-helper: wrapper program used by AppArmor security driver.
4
 *
5
 * Copyright (C) 2010-2011 Red Hat, Inc.
6
 * Copyright (C) 2009-2010 Canonical Ltd.
J
Jamie Strandboge 已提交
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 * See COPYING.LIB for the License of this software
 *
 * Author:
 *   Jamie Strandboge <jamie@canonical.com>
 *
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
24
#include <sys/stat.h>
J
Jamie Strandboge 已提交
25 26 27
#include <fcntl.h>
#include <getopt.h>
#include <sys/utsname.h>
28
#include <locale.h>
J
Jamie Strandboge 已提交
29 30 31 32 33 34 35 36 37 38 39 40 41

#include "internal.h"
#include "buf.h"
#include "util.h"
#include "memory.h"

#include "security_driver.h"
#include "security_apparmor.h"
#include "domain_conf.h"
#include "xml.h"
#include "uuid.h"
#include "hostusb.h"
#include "pci.h"
42
#include "files.h"
43
#include "configmake.h"
J
Jamie Strandboge 已提交
44

45 46
#define VIR_FROM_THIS VIR_FROM_SECURITY

J
Jamie Strandboge 已提交
47 48 49
static char *progname;

typedef struct {
50
    bool allowDiskFormatProbing;
J
Jamie Strandboge 已提交
51 52 53 54 55 56 57 58 59 60
    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 */
    char *hvm;                  /* type of hypervisor (eg hvm, xen) */
61
    char *arch;                 /* machine architecture */
J
Jamie Strandboge 已提交
62
    int bits;                   /* bits in the guest */
63 64
    char *newfile;              /* newly added file */
    bool append;                /* append to .files instead of rewrite */
J
Jamie Strandboge 已提交
65 66 67 68 69 70 71 72 73
} vahControl;

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

    VIR_FREE(ctl->def);
74
    virCapabilitiesFree(ctl->caps);
75 76 77
    VIR_FREE(ctl->files);
    VIR_FREE(ctl->hvm);
    VIR_FREE(ctl->arch);
78
    VIR_FREE(ctl->newfile);
J
Jamie Strandboge 已提交
79 80 81 82 83 84 85 86 87 88

    return 0;
}

/*
 * Print usage
 */
static void
vah_usage(void)
{
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    printf(_("\n%s [options] [< def.xml]\n\n"
            "  Options:\n"
            "    -a | --add                     load profile\n"
            "    -c | --create                  create profile from template\n"
            "    -D | --delete                  unload and delete profile\n"
            "    -f | --add-file <file>         add file to profile\n"
            "    -F | --append-file <file>      append file to profile\n"
            "    -r | --replace                 reload profile\n"
            "    -R | --remove                  unload profile\n"
            "    -h | --help                    this help\n"
            "    -u | --uuid <uuid>             uuid (profile name)\n"
            "\n"), progname);

    puts(_("This command is intended to be used by libvirtd "
           "and not used directly.\n"));
J
Jamie Strandboge 已提交
104 105 106 107 108 109
    return;
}

static void
vah_error(vahControl * ctl, int doexit, const char *str)
{
110
    fprintf(stderr, _("%s: error: %s%c"), progname, str, '\n');
J
Jamie Strandboge 已提交
111 112 113 114 115 116 117 118 119 120 121

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

static void
vah_warning(const char *str)
{
122
    fprintf(stderr, _("%s: warning: %s%c"), progname, str, '\n');
J
Jamie Strandboge 已提交
123 124 125 126 127
}

static void
vah_info(const char *str)
{
128
    fprintf(stderr, _("%s:\n%s%c"), progname, str, '\n');
J
Jamie Strandboge 已提交
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
}

/*
 * Replace @oldstr in @orig with @repstr
 * @len is number of bytes allocated for @orig. Assumes @orig, @oldstr and
 * @repstr are null terminated
 */
static int
replace_string(char *orig, const size_t len, const char *oldstr,
               const char *repstr)
{
    int idx;
    char *pos = NULL;
    char *tmp = NULL;

    if ((pos = strstr(orig, oldstr)) == NULL) {
145
        vah_error(NULL, 0, _("could not find replacement string"));
J
Jamie Strandboge 已提交
146 147 148 149
        return -1;
    }

    if (VIR_ALLOC_N(tmp, len) < 0) {
150
        vah_error(NULL, 0, _("could not allocate memory for string"));
J
Jamie Strandboge 已提交
151 152 153 154 155 156 157 158 159 160 161
        return -1;
    }
    tmp[0] = '\0';

    idx = abs(pos - orig);

    /* copy everything up to oldstr */
    strncat(tmp, orig, idx);

    /* add the replacement string */
    if (strlen(tmp) + strlen(repstr) > len - 1) {
162
        vah_error(NULL, 0, _("not enough space in target buffer"));
J
Jamie Strandboge 已提交
163 164 165 166 167 168 169
        VIR_FREE(tmp);
        return -1;
    }
    strcat(tmp, repstr);

    /* add everything after oldstr */
    if (strlen(tmp) + strlen(orig) - (idx + strlen(oldstr)) > len - 1) {
170
        vah_error(NULL, 0, _("not enough space in target buffer"));
J
Jamie Strandboge 已提交
171 172 173 174 175 176 177
        VIR_FREE(tmp);
        return -1;
    }
    strncat(tmp, orig + idx + strlen(oldstr),
            strlen(orig) - (idx + strlen(oldstr)));

    if (virStrcpy(orig, tmp, len) == NULL) {
178
        vah_error(NULL, 0, _("error replacing string"));
J
Jamie Strandboge 已提交
179 180 181 182 183 184 185 186 187 188 189 190 191 192
        VIR_FREE(tmp);
        return -1;
    }
    VIR_FREE(tmp);

    return 0;
}

/*
 * run an apparmor_parser command
 */
static int
parserCommand(const char *profile_name, const char cmd)
{
193
    int result = -1;
J
Jamie Strandboge 已提交
194
    char flag[3];
195
    char *profile;
196 197
    int status;
    int ret;
J
Jamie Strandboge 已提交
198 199

    if (strchr("arR", cmd) == NULL) {
200
        vah_error(NULL, 0, _("invalid flag"));
J
Jamie Strandboge 已提交
201 202 203 204 205
        return -1;
    }

    snprintf(flag, 3, "-%c", cmd);

206 207
    if (virAsprintf(&profile, "%s/%s",
                    APPARMOR_DIR "/libvirt", profile_name) < 0) {
208
        vah_error(NULL, 0, _("profile name exceeds maximum length"));
J
Jamie Strandboge 已提交
209 210 211 212
        return -1;
    }

    if (!virFileExists(profile)) {
213
        vah_error(NULL, 0, _("profile does not exist"));
214
        goto cleanup;
J
Jamie Strandboge 已提交
215 216 217 218
    } else {
        const char * const argv[] = {
            "/sbin/apparmor_parser", flag, profile, NULL
        };
219 220 221
        if ((ret = virRun(argv, &status)) != 0 ||
            (WIFEXITED(status) && WEXITSTATUS(status) != 0)) {
            if (ret != 0) {
222
                vah_error(NULL, 0, _("failed to run apparmor_parser"));
223
                goto cleanup;
224 225 226
            } else if (cmd == 'R' && WIFEXITED(status) &&
                       WEXITSTATUS(status) == 234) {
                vah_warning(_("unable to unload already unloaded profile"));
227
            } else {
228
                vah_error(NULL, 0, _("apparmor_parser exited with error"));
229
                goto cleanup;
230
            }
J
Jamie Strandboge 已提交
231 232 233
        }
    }

234 235 236 237 238 239
    result = 0;

cleanup:
    VIR_FREE(profile);

    return result;
J
Jamie Strandboge 已提交
240 241 242 243 244 245
}

/*
 * Update the dynamic files
 */
static int
246 247
update_include_file(const char *include_file, const char *included_files,
                    bool append)
J
Jamie Strandboge 已提交
248 249
{
    int rc = -1;
250
    int plen, flen = 0;
J
Jamie Strandboge 已提交
251 252
    int fd;
    char *pcontent = NULL;
253
    char *existing = NULL;
J
Jamie Strandboge 已提交
254 255 256
    const char *warning =
         "# DO NOT EDIT THIS FILE DIRECTLY. IT IS MANAGED BY LIBVIRT.\n";

257 258 259 260 261 262 263 264
    if (virFileExists(include_file)) {
        flen = virFileReadAll(include_file, MAX_FILE_LEN, &existing);
        if (flen < 0)
            return rc;
    }

    if (append && virFileExists(include_file)) {
        if (virAsprintf(&pcontent, "%s%s", existing, included_files) == -1) {
265
            vah_error(NULL, 0, _("could not allocate memory for profile"));
266 267 268 269
            goto clean;
        }
    } else {
        if (virAsprintf(&pcontent, "%s%s", warning, included_files) == -1) {
270
            vah_error(NULL, 0, _("could not allocate memory for profile"));
271 272
            goto clean;
        }
J
Jamie Strandboge 已提交
273 274 275 276
    }

    plen = strlen(pcontent);
    if (plen > MAX_FILE_LEN) {
277
        vah_error(NULL, 0, _("invalid length for new profile"));
J
Jamie Strandboge 已提交
278 279 280 281
        goto clean;
    }

    /* only update the disk profile if it is different */
282 283 284
    if (flen > 0 && flen == plen && STREQLEN(existing, pcontent, plen)) {
        rc = 0;
        goto clean;
J
Jamie Strandboge 已提交
285 286 287 288
    }

    /* write the file */
    if ((fd = open(include_file, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1) {
289
        vah_error(NULL, 0, _("failed to create include file"));
J
Jamie Strandboge 已提交
290 291 292 293
        goto clean;
    }

    if (safewrite(fd, pcontent, plen) < 0) { /* don't write the '\0' */
294
        VIR_FORCE_CLOSE(fd);
295
        vah_error(NULL, 0, _("failed to write to profile"));
J
Jamie Strandboge 已提交
296 297 298
        goto clean;
    }

299
    if (VIR_CLOSE(fd) != 0) {
300
        vah_error(NULL, 0, _("failed to close or write to profile"));
J
Jamie Strandboge 已提交
301 302 303 304 305 306
        goto clean;
    }
    rc = 0;

  clean:
    VIR_FREE(pcontent);
307
    VIR_FREE(existing);
J
Jamie Strandboge 已提交
308 309 310 311 312 313 314 315 316 317 318

    return rc;
}

/*
 * Create a profile based on a template
 */
static int
create_profile(const char *profile, const char *profile_name,
               const char *profile_files)
{
319
    char *template;
J
Jamie Strandboge 已提交
320 321 322 323 324 325 326 327 328 329 330
    char *tcontent = NULL;
    char *pcontent = NULL;
    char *replace_name = NULL;
    char *replace_files = NULL;
    const char *template_name = "\nprofile LIBVIRT_TEMPLATE";
    const char *template_end = "\n}";
    int tlen, plen;
    int fd;
    int rc = -1;

    if (virFileExists(profile)) {
331
        vah_error(NULL, 0, _("profile exists"));
J
Jamie Strandboge 已提交
332 333 334
        goto end;
    }

335
    if (virAsprintf(&template, "%s/TEMPLATE", APPARMOR_DIR "/libvirt") < 0) {
336
        vah_error(NULL, 0, _("template name exceeds maximum length"));
J
Jamie Strandboge 已提交
337 338 339 340
        goto end;
    }

    if (!virFileExists(template)) {
341
        vah_error(NULL, 0, _("template does not exist"));
J
Jamie Strandboge 已提交
342 343 344 345
        goto end;
    }

    if ((tlen = virFileReadAll(template, MAX_FILE_LEN, &tcontent)) < 0) {
346
        vah_error(NULL, 0, _("failed to read AppArmor template"));
J
Jamie Strandboge 已提交
347 348 349 350
        goto end;
    }

    if (strstr(tcontent, template_name) == NULL) {
351
        vah_error(NULL, 0, _("no replacement string in template"));
J
Jamie Strandboge 已提交
352 353 354 355
        goto clean_tcontent;
    }

    if (strstr(tcontent, template_end) == NULL) {
356
        vah_error(NULL, 0, _("no replacement string in template"));
J
Jamie Strandboge 已提交
357 358 359 360 361
        goto clean_tcontent;
    }

    /* '\nprofile <profile_name>\0' */
    if (virAsprintf(&replace_name, "\nprofile %s", profile_name) == -1) {
362
        vah_error(NULL, 0, _("could not allocate memory for profile name"));
J
Jamie Strandboge 已提交
363 364 365 366 367
        goto clean_tcontent;
    }

    /* '\n<profile_files>\n}\0' */
    if (virAsprintf(&replace_files, "\n%s\n}", profile_files) == -1) {
368
        vah_error(NULL, 0, _("could not allocate memory for profile files"));
J
Jamie Strandboge 已提交
369 370 371 372 373 374 375
        VIR_FREE(replace_name);
        goto clean_tcontent;
    }

    plen = tlen + strlen(replace_name) - strlen(template_name) +
           strlen(replace_files) - strlen(template_end) + 1;
    if (plen > MAX_FILE_LEN || plen < tlen) {
376
        vah_error(NULL, 0, _("invalid length for new profile"));
J
Jamie Strandboge 已提交
377 378 379 380
        goto clean_replace;
    }

    if (VIR_ALLOC_N(pcontent, plen) < 0) {
381
        vah_error(NULL, 0, _("could not allocate memory for profile"));
J
Jamie Strandboge 已提交
382 383 384 385 386 387 388 389 390 391 392 393 394
        goto clean_replace;
    }
    pcontent[0] = '\0';
    strcpy(pcontent, tcontent);

    if (replace_string(pcontent, plen, template_name, replace_name) < 0)
        goto clean_all;

    if (replace_string(pcontent, plen, template_end, replace_files) < 0)
        goto clean_all;

    /* write the file */
    if ((fd = open(profile, O_CREAT | O_EXCL | O_WRONLY, 0644)) == -1) {
395
        vah_error(NULL, 0, _("failed to create profile"));
J
Jamie Strandboge 已提交
396 397 398 399
        goto clean_all;
    }

    if (safewrite(fd, pcontent, plen - 1) < 0) { /* don't write the '\0' */
400
        VIR_FORCE_CLOSE(fd);
401
        vah_error(NULL, 0, _("failed to write to profile"));
J
Jamie Strandboge 已提交
402 403 404
        goto clean_all;
    }

405
    if (VIR_CLOSE(fd) != 0) {
406
        vah_error(NULL, 0, _("failed to close or write to profile"));
J
Jamie Strandboge 已提交
407 408 409 410 411 412 413 414 415 416 417 418
        goto clean_all;
    }
    rc = 0;

  clean_all:
    VIR_FREE(pcontent);
  clean_replace:
    VIR_FREE(replace_name);
    VIR_FREE(replace_files);
  clean_tcontent:
    VIR_FREE(tcontent);
  end:
419
    VIR_FREE(template);
J
Jamie Strandboge 已提交
420 421 422 423 424 425 426 427 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 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
    return rc;
}

/*
 * 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 */
    const char *bad = " /[]*";

    if (strlen(name) == 0 || strlen(name) > PATH_MAX - 1)
        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)
{
    int i;
    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)
{
    struct stat sb;
514
    int npaths, opaths;
J
Jamie Strandboge 已提交
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
    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",
        "/initrd.img"
    };
540 541 542 543
    /* override the above with these */
    const char * const override[] = {
        "/sys/devices/pci"	/* for hostdev pci devices */
    };
J
Jamie Strandboge 已提交
544 545

    if (path == NULL || strlen(path) > PATH_MAX - 1) {
546
        vah_error(NULL, 0, _("bad pathname"));
J
Jamie Strandboge 已提交
547 548 549 550 551 552 553 554 555
        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 已提交
556 557 558 559
    /* Require an absolute path */
    if (STRNEQLEN(path, "/", 1))
        return 1;

J
Jamie Strandboge 已提交
560
    if (!virFileExists(path))
561
        vah_warning(_("path does not exist, skipping file type checks"));
J
Jamie Strandboge 已提交
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
    else {
        if (stat(path, &sb) == -1)
            return -1;

        switch (sb.st_mode & S_IFMT) {
            case S_IFDIR:
                return 1;
                break;
            case S_IFIFO:
                return 1;
                break;
            case S_IFSOCK:
                return 1;
                break;
            default:
                break;
        }
    }

581 582
    opaths = sizeof(override)/sizeof *(override);

J
Jamie Strandboge 已提交
583
    npaths = sizeof(restricted)/sizeof *(restricted);
584 585 586
    if (array_starts_with(path, restricted, npaths) == 0 &&
        array_starts_with(path, override, opaths) != 0)
            return 1;
J
Jamie Strandboge 已提交
587 588 589 590 591 592 593 594 595 596

    npaths = sizeof(restricted_rw)/sizeof *(restricted_rw);
    if (!readonly) {
        if (array_starts_with(path, restricted_rw, npaths) == 0)
            return 1;
    }

    return 0;
}

597 598 599 600 601 602 603
static int
verify_xpath_context(xmlXPathContextPtr ctxt)
{
    int rc = -1;
    char *tmp = NULL;

    if (!ctxt) {
604
        vah_warning(_("Invalid context"));
605 606 607 608 609
        goto error;
    }

    /* check if have <name> */
    if (!(tmp = virXPathString("string(./name[1])", ctxt))) {
610
        vah_warning(_("Could not find <name>"));
611 612 613 614 615 616
        goto error;
    }
    VIR_FREE(tmp);

    /* check if have <uuid> */
    if (!(tmp = virXPathString("string(./uuid[1])", ctxt))) {
617
        vah_warning(_("Could not find <uuid>"));
618 619 620 621 622 623 624 625 626 627
        goto error;
    }
    VIR_FREE(tmp);

    rc = 0;

  error:
    return rc;
}

628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
/*
 * Parse the xml we received to fill in the following:
 * ctl->hvm
 * ctl->arch
 * ctl->bits
 *
 * 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;
    xmlNodePtr root;

644
    if (!(xml = virXMLParseString(xmlStr, "domain.xml"))) {
645 646 647
        goto cleanup;
    }

648
    root = xmlDocGetRootElement(xml);
649
    if (!xmlStrEqual(root->name, BAD_CAST "domain")) {
650
        vah_error(NULL, 0, _("incorrect root element"));
651 652 653 654
        goto cleanup;
    }

    if ((ctxt = xmlXPathNewContext(xml)) == NULL) {
655
        vah_error(ctl, 0, _("could not allocate memory"));
656 657 658 659
        goto cleanup;
    }
    ctxt->node = root;

660 661 662 663
    /* Quick sanity check for some required elements */
    if (verify_xpath_context(ctxt) != 0)
        goto cleanup;

664
    ctl->hvm = virXPathString("string(./os/type[1])", ctxt);
665
    if (!ctl->hvm || STRNEQ(ctl->hvm, "hvm")) {
666
        vah_error(ctl, 0, _("os.type is not 'hvm'"));
667 668
        goto cleanup;
    }
669
    ctl->arch = virXPathString("string(./os/type[1]/@arch)", ctxt);
670 671 672 673 674 675 676 677 678
    if (!ctl->arch) {
        /* The XML we are given should have an arch, but in case it doesn't,
         * just use the host's arch.
         */
        struct utsname utsname;

        /* Really, this never fails - look at the man-page. */
        uname (&utsname);
        if ((ctl->arch = strdup(utsname.machine)) == NULL) {
679
            vah_error(ctl, 0, _("could not allocate memory"));
680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
            goto cleanup;
        }
    }
    if (STREQ(ctl->arch, "x86_64"))
        ctl->bits = 64;
    else
        ctl->bits = 32;

    rc = 0;

  cleanup:
    xmlFreeDoc (xml);
    xmlXPathFreeContext(ctxt);

    return rc;
}

J
Jamie Strandboge 已提交
697 698 699 700 701 702 703 704 705 706
static int
get_definition(vahControl * ctl, const char *xmlStr)
{
    int rc = -1;
    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().
     */
707 708
    if (caps_mockup(ctl, xmlStr) != 0)
        goto exit;
J
Jamie Strandboge 已提交
709

710
    if ((ctl->caps = virCapabilitiesNew(ctl->arch, 1, 1)) == NULL) {
711
        vah_error(ctl, 0, _("could not allocate memory"));
J
Jamie Strandboge 已提交
712 713 714 715 716
        goto exit;
    }

    if ((guest = virCapabilitiesAddGuest(ctl->caps,
                                         ctl->hvm,
717
                                         ctl->arch,
J
Jamie Strandboge 已提交
718 719 720 721 722
                                         ctl->bits,
                                         NULL,
                                         NULL,
                                         0,
                                         NULL)) == NULL) {
723
        vah_error(ctl, 0, _("could not allocate memory"));
J
Jamie Strandboge 已提交
724 725 726
        goto exit;
    }

727 728
    ctl->def = virDomainDefParseString(ctl->caps, xmlStr,
                                       VIR_DOMAIN_XML_INACTIVE);
J
Jamie Strandboge 已提交
729
    if (ctl->def == NULL) {
730
        vah_error(ctl, 0, _("could not parse XML"));
J
Jamie Strandboge 已提交
731 732 733 734
        goto exit;
    }

    if (!ctl->def->name) {
735
        vah_error(ctl, 0, _("could not find name in XML"));
J
Jamie Strandboge 已提交
736 737 738 739
        goto exit;
    }

    if (valid_name(ctl->def->name) != 0) {
740
        vah_error(ctl, 0, _("bad name"));
J
Jamie Strandboge 已提交
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
        goto exit;
    }

    rc = 0;

  exit:
    return rc;
}

static int
vah_add_file(virBufferPtr buf, const char *path, const char *perms)
{
    char *tmp = NULL;
    int rc = -1;
    bool readonly = true;

    if (path == NULL)
        return rc;

J
Jamie Strandboge 已提交
760 761 762 763 764 765
    /* 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);
766
        vah_warning(_("  skipped non-absolute path"));
J
Jamie Strandboge 已提交
767 768 769
        return 0;
    }

J
Jamie Strandboge 已提交
770 771 772
    if (virFileExists(path)) {
        if ((tmp = realpath(path, NULL)) == NULL) {
            vah_error(NULL, 0, path);
773
            vah_error(NULL, 0, _("  could not find realpath for disk"));
J
Jamie Strandboge 已提交
774 775 776 777 778 779 780 781 782 783 784 785 786
            return rc;
        }
    } else
        if ((tmp = strdup(path)) == NULL)
            return rc;

    if (strchr(perms, 'w') != NULL)
        readonly = false;

    rc = valid_path(tmp, readonly);
    if (rc != 0) {
        if (rc > 0) {
            vah_error(NULL, 0, path);
787
            vah_error(NULL, 0, _("  skipped restricted file"));
J
Jamie Strandboge 已提交
788 789 790 791
        }
        goto clean;
    }

792
    virBufferAsprintf(buf, "  \"%s\" %s,\n", tmp, perms);
793
    if (readonly) {
794 795
        virBufferAsprintf(buf, "  # don't audit writes to readonly files\n");
        virBufferAsprintf(buf, "  deny \"%s\" w,\n", tmp);
796
    }
J
Jamie Strandboge 已提交
797 798

  clean:
799
    VIR_FREE(tmp);
J
Jamie Strandboge 已提交
800 801 802 803 804

    return rc;
}

static int
805 806 807 808 809 810 811 812 813 814
file_iterate_hostdev_cb(usbDevice *dev ATTRIBUTE_UNUSED,
                        const char *file, void *opaque)
{
    virBufferPtr buf = opaque;
    return vah_add_file(buf, file, "rw");
}

static int
file_iterate_pci_cb(pciDevice *dev ATTRIBUTE_UNUSED,
                        const char *file, void *opaque)
J
Jamie Strandboge 已提交
815 816 817 818 819
{
    virBufferPtr buf = opaque;
    return vah_add_file(buf, file, "rw");
}

820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
static int
add_file_path(virDomainDiskDefPtr disk,
              const char *path,
              size_t depth,
              void *opaque)
{
    virBufferPtr buf = opaque;
    int ret;

    if (depth == 0) {
        if (disk->readonly)
            ret = vah_add_file(buf, path, "r");
        else
            ret = vah_add_file(buf, path, "rw");
    } else {
        ret = vah_add_file(buf, path, "r");
    }

838 839 840
    if (ret != 0)
        ret = -1;

841 842 843 844
    return ret;
}


J
Jamie Strandboge 已提交
845 846 847 848 849 850 851 852 853 854 855 856
static int
get_files(vahControl * ctl)
{
    virBuffer buf = VIR_BUFFER_INITIALIZER;
    int rc = -1;
    int i;
    char *uuid;
    char uuidstr[VIR_UUID_STRING_BUFLEN];

    /* verify uuid is same as what we were given on the command line */
    virUUIDFormat(ctl->def->uuid, uuidstr);
    if (virAsprintf(&uuid, "%s%s", AA_PREFIX, uuidstr) == -1) {
857
        vah_error(ctl, 0, _("could not allocate memory"));
J
Jamie Strandboge 已提交
858 859 860 861
        return rc;
    }

    if (STRNEQ(uuid, ctl->uuid)) {
862
        vah_error(ctl, 0, _("given uuid does not match XML uuid"));
J
Jamie Strandboge 已提交
863 864 865
        goto clean;
    }

866
    for (i = 0; i < ctl->def->ndisks; i++) {
867 868 869 870
        /* XXX passing ignoreOpenFailure = true to get back to the behavior
         * from before using virDomainDiskDefForeachPath. actually we should
         * be passing ignoreOpenFailure = false and handle open errors more
         * careful than just ignoring them */
871
        int ret = virDomainDiskDefForeachPath(ctl->def->disks[i],
872
                                              ctl->allowDiskFormatProbing,
873
                                              true,
874 875 876 877 878
                                              add_file_path,
                                              &buf);
        if (ret != 0)
            goto clean;
    }
J
Jamie Strandboge 已提交
879 880

    for (i = 0; i < ctl->def->nserials; i++)
881
        if (ctl->def->serials[i] &&
882 883 884 885 886
            (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_PIPE) &&
            ctl->def->serials[i]->source.data.file.path)
J
Jamie Strandboge 已提交
887
            if (vah_add_file(&buf,
888
                             ctl->def->serials[i]->source.data.file.path, "rw") != 0)
J
Jamie Strandboge 已提交
889 890
                goto clean;

891 892
    if (ctl->def->console && ctl->def->console->source.data.file.path)
        if (vah_add_file(&buf, ctl->def->console->source.data.file.path, "rw") != 0)
J
Jamie Strandboge 已提交
893 894
            goto clean;

895 896
    for (i = 0 ; i < ctl->def->nparallels; i++)
        if (ctl->def->parallels[i] &&
897 898 899 900 901
            (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_PIPE) &&
            ctl->def->parallels[i]->source.data.file.path)
902
            if (vah_add_file(&buf,
903
                             ctl->def->parallels[i]->source.data.file.path,
904 905 906 907 908
                             "rw") != 0)
                goto clean;

    for (i = 0 ; i < ctl->def->nchannels; i++)
        if (ctl->def->channels[i] &&
909 910 911 912 913
            (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_PIPE) &&
            ctl->def->channels[i]->source.data.file.path)
914
            if (vah_add_file(&buf,
915
                             ctl->def->channels[i]->source.data.file.path,
916 917 918
                             "rw") != 0)
                goto clean;

919
    if (ctl->def->os.kernel)
J
Jamie Strandboge 已提交
920 921 922
        if (vah_add_file(&buf, ctl->def->os.kernel, "r") != 0)
            goto clean;

923
    if (ctl->def->os.initrd)
J
Jamie Strandboge 已提交
924 925 926 927 928
        if (vah_add_file(&buf, ctl->def->os.initrd, "r") != 0)
            goto clean;

    if (ctl->def->os.loader && ctl->def->os.loader)
        if (vah_add_file(&buf, ctl->def->os.loader, "r") != 0)
929 930 931 932 933 934
            goto clean;

    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)
J
Jamie Strandboge 已提交
935 936 937 938 939 940 941
            goto clean;

    for (i = 0; i < ctl->def->nhostdevs; i++)
        if (ctl->def->hostdevs[i]) {
            virDomainHostdevDefPtr dev = ctl->def->hostdevs[i];
            switch (dev->source.subsys.type) {
            case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: {
942
                usbDevice *usb = usbGetDevice(dev->source.subsys.u.usb.bus,
943
                                              dev->source.subsys.u.usb.device);
944 945 946 947

                if (usb == NULL)
                    continue;

948
                rc = usbDeviceFileIterate(usb, file_iterate_hostdev_cb, &buf);
949
                usbFreeDevice(usb);
950 951
                if (rc != 0)
                    goto clean;
J
Jamie Strandboge 已提交
952 953
                break;
            }
954

J
Jamie Strandboge 已提交
955
            case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: {
956
                pciDevice *pci = pciGetDevice(
J
Jamie Strandboge 已提交
957 958 959 960 961 962 963 964
                           dev->source.subsys.u.pci.domain,
                           dev->source.subsys.u.pci.bus,
                           dev->source.subsys.u.pci.slot,
                           dev->source.subsys.u.pci.function);

                if (pci == NULL)
                    continue;

965
                rc = pciDeviceFileIterate(pci, file_iterate_pci_cb, &buf);
966
                pciFreeDevice(pci);
J
Jamie Strandboge 已提交
967 968 969

                break;
            }
970

J
Jamie Strandboge 已提交
971 972 973 974 975 976
            default:
                rc = 0;
                break;
            } /* switch */
        }

977 978
    if (ctl->newfile)
        if (vah_add_file(&buf, ctl->newfile, "rw") != 0)
J
Jamie Strandboge 已提交
979 980 981
            goto clean;

    if (virBufferError(&buf)) {
982
        virBufferFreeAndReset(&buf);
983
        vah_error(NULL, 0, _("failed to allocate file buffer"));
984
        goto clean;
J
Jamie Strandboge 已提交
985 986 987 988 989 990 991 992 993 994 995 996 997 998 999
    }

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

  clean:
    VIR_FREE(uuid);
    return rc;
}

static int
vahParseArgv(vahControl * ctl, int argc, char **argv)
{
    int arg, idx = 0;
    struct option opt[] = {
1000
        {"probing", 1, 0, 'p' },
J
Jamie Strandboge 已提交
1001 1002 1003 1004 1005
        {"add", 0, 0, 'a'},
        {"create", 0, 0, 'c'},
        {"dryrun", 0, 0, 'd'},
        {"delete", 0, 0, 'D'},
        {"add-file", 0, 0, 'f'},
1006
        {"append-file", 0, 0, 'F'},
J
Jamie Strandboge 已提交
1007 1008 1009 1010 1011 1012 1013
        {"help", 0, 0, 'h'},
        {"replace", 0, 0, 'r'},
        {"remove", 0, 0, 'R'},
        {"uuid", 1, 0, 'u'},
        {0, 0, 0, 0}
    };

1014
    while ((arg = getopt_long(argc, argv, "acdDhrRH:b:u:p:f:F:", opt,
J
Jamie Strandboge 已提交
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
            &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':
1030 1031
            case 'F':
                if ((ctl->newfile = strdup(optarg)) == NULL)
1032
                    vah_error(ctl, 1, _("could not allocate memory for disk"));
1033
                ctl->append = arg == 'F';
J
Jamie Strandboge 已提交
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
                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)
1047
                    vah_error(ctl, 1, _("invalid UUID"));
J
Jamie Strandboge 已提交
1048 1049
                if (virStrcpy((char *) ctl->uuid, optarg,
                    PROFILE_NAME_SIZE) == NULL)
1050
                    vah_error(ctl, 1, _("error copying UUID"));
J
Jamie Strandboge 已提交
1051
                break;
1052 1053 1054 1055 1056 1057
            case 'p':
                if (STREQ(optarg, "1"))
                    ctl->allowDiskFormatProbing = true;
                else
                    ctl->allowDiskFormatProbing = false;
                break;
J
Jamie Strandboge 已提交
1058
            default:
1059
                vah_error(ctl, 1, _("unsupported option"));
J
Jamie Strandboge 已提交
1060 1061 1062 1063
                break;
        }
    }
    if (strchr("acDrR", ctl->cmd) == NULL)
1064
        vah_error(ctl, 1, _("bad command"));
J
Jamie Strandboge 已提交
1065 1066

    if (valid_uuid(ctl->uuid) != 0)
1067
        vah_error(ctl, 1, _("invalid UUID"));
J
Jamie Strandboge 已提交
1068 1069 1070 1071 1072 1073 1074 1075 1076

    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)
1077
            vah_error(ctl, 1, _("could not read xml file"));
J
Jamie Strandboge 已提交
1078 1079 1080

        if (get_definition(ctl, xmlStr) != 0 || ctl->def == NULL) {
            VIR_FREE(xmlStr);
1081
            vah_error(ctl, 1, _("could not get VM definition"));
J
Jamie Strandboge 已提交
1082 1083 1084 1085
        }
        VIR_FREE(xmlStr);

        if (get_files(ctl) != 0)
1086
            vah_error(ctl, 1, _("invalid VM definition"));
J
Jamie Strandboge 已提交
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
    }
    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;
1105 1106
    char *profile = NULL;
    char *include_file = NULL;
J
Jamie Strandboge 已提交
1107

E
Eric Blake 已提交
1108 1109 1110
    if (setlocale(LC_ALL, "") == NULL ||
        bindtextdomain(PACKAGE, LOCALEDIR) == NULL ||
        textdomain(PACKAGE) == NULL) {
1111
        fprintf(stderr, _("%s: initialization failed\n"), argv[0]);
E
Eric Blake 已提交
1112 1113 1114
        exit(EXIT_FAILURE);
    }

J
Jamie Strandboge 已提交
1115 1116 1117
    /* clear the environment */
    environ = NULL;
    if (setenv("PATH", "/sbin:/usr/sbin", 1) != 0) {
1118
        vah_error(ctl, 1, _("could not set PATH"));
J
Jamie Strandboge 已提交
1119
    }
1120

J
Jamie Strandboge 已提交
1121
    if (setenv("IFS", " \t\n", 1) != 0) {
1122
        vah_error(ctl, 1, _("could not set IFS"));
J
Jamie Strandboge 已提交
1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
    }

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

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

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

1135 1136 1137
    if (virAsprintf(&profile, "%s/%s",
                    APPARMOR_DIR "/libvirt", ctl->uuid) < 0)
        vah_error(ctl, 0, _("could not allocate memory"));
J
Jamie Strandboge 已提交
1138

1139 1140 1141
    if (virAsprintf(&include_file, "%s/%s.files",
                    APPARMOR_DIR "/libvirt", ctl->uuid) < 0)
        vah_error(ctl, 0, _("could not allocate memory"));
J
Jamie Strandboge 已提交
1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153

    if (ctl->cmd == 'a')
        rc = parserLoad(ctl->uuid);
    else if (ctl->cmd == 'R' || ctl->cmd == 'D') {
        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;

1154 1155 1156
        if (ctl->cmd == 'c' && virFileExists(profile)) {
            vah_error(ctl, 1, _("profile exists"));
        }
J
Jamie Strandboge 已提交
1157

1158 1159 1160 1161
        if (ctl->append && ctl->newfile) {
            if (vah_add_file(&buf, ctl->newfile, "rw") != 0)
                goto clean;
        } else {
1162
            virBufferAsprintf(&buf, "  \"%s/log/libvirt/**/%s.log\" w,\n",
1163
                              LOCALSTATEDIR, ctl->def->name);
1164
            virBufferAsprintf(&buf, "  \"%s/lib/libvirt/**/%s.monitor\" rw,\n",
1165
                              LOCALSTATEDIR, ctl->def->name);
1166
            virBufferAsprintf(&buf, "  \"%s/run/libvirt/**/%s.pid\" rwk,\n",
1167
                              LOCALSTATEDIR, ctl->def->name);
1168
            if (ctl->files)
1169
                virBufferAdd(&buf, ctl->files, -1);
1170
        }
J
Jamie Strandboge 已提交
1171

1172 1173
        if (virBufferError(&buf)) {
            virBufferFreeAndReset(&buf);
1174
            vah_error(ctl, 1, _("failed to allocate buffer"));
1175
        }
J
Jamie Strandboge 已提交
1176 1177 1178 1179 1180 1181 1182 1183 1184

        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;
        } else if ((rc = update_include_file(include_file,
1185 1186
                                             included_files,
                                             ctl->append)) != 0)
J
Jamie Strandboge 已提交
1187 1188 1189 1190 1191 1192 1193 1194
            goto clean;


        /* create the profile from TEMPLATE */
        if (ctl->cmd == 'c') {
            char *tmp = NULL;
            if (virAsprintf(&tmp, "  #include <libvirt/%s.files>\n",
                            ctl->uuid) == -1) {
1195
                vah_error(ctl, 0, _("could not allocate memory"));
J
Jamie Strandboge 已提交
1196 1197 1198 1199 1200 1201 1202 1203 1204
                goto clean;
            }

            if (ctl->dryrun) {
                vah_info(profile);
                vah_info(ctl->uuid);
                vah_info(tmp);
                rc = 0;
            } else if ((rc = create_profile(profile, ctl->uuid, tmp)) != 0) {
1205
                vah_error(ctl, 0, _("could not create profile"));
J
Jamie Strandboge 已提交
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228
                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);
            }
        }
      clean:
        VIR_FREE(included_files);
    }

    vahDeinit(ctl);
1229 1230 1231 1232

    VIR_FREE(profile);
    VIR_FREE(include_file);

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