# apparmor.d - Full set of apparmor profiles
# Copyright (C) Libvirt Team
# Copyright (C) 2021-2024 Alexandre Pujol <alexandre@pujol.io>
# SPDX-License-Identifier: GPL-2.0-only

# Based on Libvirt Apparmor profile, it is largelly restricted from it.
# As upstream profile mostly focus on confining the guests. Not libvirt itself.
# It uses a lot of profiles provided by apparmor.d
# Source: https://gitlab.com/libvirt/libvirt/-/blob/master/src/security/apparmor/usr.sbin.libvirtd.in

# Warning: Such a profile is limited as it gives access to a lot of resources.

abi <abi/3.0>,

include <tunables/global>

@{exec_path} = @{bin}/libvirtd
profile libvirtd @{exec_path} flags=(attach_disconnected) {
  include <abstractions/base>
  include <abstractions/consoles>
  include <abstractions/dbus>
  include <abstractions/devices-usb>
  include <abstractions/disks-write>
  include <abstractions/nameservice-strict>

  capability audit_write,
  capability bpf,
  capability chown,
  capability dac_override,
  capability dac_read_search,
  capability fowner,
  capability fsetid,
  capability ipc_lock,
  capability kill,
  capability mknod,
  capability net_admin,
  capability net_raw,
  capability perfmon,
  capability setgid,
  capability setpcap,
  capability setuid,
  capability sys_admin,
  capability sys_chroot,
  capability sys_module,
  capability sys_nice,
  capability sys_pacct,
  capability sys_ptrace,
  capability sys_rawio,
  capability sys_resource,

  network inet stream,
  network inet dgram,
  network inet6 stream,
  network inet6 dgram,
  network netlink raw,
  network packet dgram,
  network packet raw,

  mount options=(rw, rslave) -> /,
  mount options=(rw, nosuid) -> @{run}/libvirt/qemu/*.dev/,
  umount @{run}/libvirt/qemu/*.dev/,

  # Libvirt provides any mounts under /dev to qemu namespaces
  mount options=(rw, move) /dev/ -> @{run}/libvirt/qemu/*.dev/,
  mount options=(rw, move) /dev/** -> @{run}/libvirt/qemu/*{,/},
  mount options=(rw, move) @{run}/libvirt/qemu/*.dev/ -> /dev/,
  mount options=(rw, move) @{run}/libvirt/qemu/*{,/} -> /dev/**,

  ptrace (read,trace) peer=@{profile_name},
  ptrace (read,trace) peer=dnsmasq,
  ptrace (read,trace) peer=libvirt-@{uuid},
  ptrace (read,trace) peer=libvirt-dbus,
  ptrace (read,trace) peer=unconfined,
  ptrace (read,trace) peer=virt-manager,

  signal (read,send) peer=libvirt-@{uuid},
  signal (read,send) peer=unconfined,
  signal (send) peer=dnsmasq,
  signal (send) set=(kill, term) peer=virtiofsd,
  signal (send) set=(term) peer=libvirtd//qemu_bridge_helper,
  signal (send) set=(term) peer=swtpm,

  unix (send, receive) type=stream addr=none peer=(label=libvirt-@{uuid}),
  unix (send, receive) type=stream addr=none peer=(label=libvirtd//qemu_bridge_helper),
  unix (send, receive) type=stream addr=none peer=(label=unconfined addr=none),
  unix (send, receive) type=stream addr=none peer=(label=unconfined),

  # Allow changing to our UUID-based named profiles
  change_profile -> libvirt-@{uuid},

  @{exec_path} mr,

  @{lib}/libvirt/libvirt_iohelper                   rix,
  @{lib}/libvirt/libvirt_parthelper                 rix,

  @{lib}/udev/scsi_id                              rPUx,
  @{lib}/xen-*/bin/libxl-save-helper               rPUx,
  @{lib}/xen-*/bin/pygrub                          rPUx,
  @{lib}/xen-common/bin/xen-toolstack              rPUx,
  @{lib}/xen/bin/*                                 rPUx,
  /{usr/,}{lib,lib64,lib/qemu,libexec}/vhost-user-gpu  rPUx,
  /{usr/,}{lib,lib64,lib/qemu,libexec}/virtiofsd        rux, # TODO: WIP

  /{usr/,}{lib,lib64,lib/qemu,libexec}/qemu-bridge-helper Cx -> qemu_bridge_helper,

  @{bin}/dmidecode                            rPx,
  @{bin}/dnsmasq                              rPx,
  @{bin}/kmod                                 rPx,
  @{bin}/lvm                                 rPUx,
  @{bin}/mdevctl                              rPx,
  @{bin}/swtpm                                rPx,
  @{bin}/swtpm_ioctl                          rPx,
  @{bin}/swtpm_setup                          rPx,
  @{bin}/udevadm                              rPx,
  @{bin}/virtiofsd                            rux, # TODO: WIP
  @{bin}/virtlogd                             rPx,

  @{sh_path}                    rix,
  @{bin}/ip                     rix,
  @{bin}/nft                    rix,
  @{bin}/qemu-img               rUx, # TODO: Integration with virt-aa-helper
  @{bin}/qemu-system*           rUx, # TODO: Integration with virt-aa-helper
  @{bin}/tc                     rix,
  @{bin}/xmllint                rix,
  @{bin}/xtables-nft-multi      rix,
  @{lib}/libvirt/virt-aa-helper rPx,

  /etc/libvirt/hooks/**    rPUx,
  /etc/xen/scripts/**      rmix,
  /var/lib/libvirt/virtd*  rix,

  /usr/share/edk2*/{,**} rk,
  /usr/share/hwdata/* r,
  /usr/share/libvirt/{,**} r,
  /usr/share/mime/mime.cache r,
  /usr/share/misc/pci.ids r,
  /usr/share/qemu/{,**} r,

  @{etc_rw}/apparmor.d/libvirt/libvirt-@{uuid} r,
  @{etc_rw}/libvirt/{,**} rw,
  /etc/mdevctl.d/{,**} r,
  /etc/sasl2/qemu.conf r,
  /etc/xml/catalog r,

  /var/cache/libvirt/{,**} rw,
  /var/lib/libvirt/{,**} rwk,
  /var/log/swtpm/libvirt/{,**} rw,

  # User VM images and share
  @{user_share_dirs}/ r,
  @{user_share_dirs}/libvirt/{,**} rwk,
  @{user_vm_dirs}/{,**} rwk,
  @{user_publicshare_dirs}/{,**} rwk,

  @{run}/libvirt/ rw,
  @{run}/libvirt/** rwk,
  @{run}/libvirtd.pid wk,
  @{run}/lock/LCK.._pts_@{int} rw,
  @{run}/systemd/inhibit/[0-9]*.ref rw,
  @{run}/systemd/notify w,
  @{run}/utmp rk,

  @{run}/udev/data/+backlight:* r,
  @{run}/udev/data/+bluetooth:* r,
  @{run}/udev/data/+dmi:* r,              # for motherboard info
  @{run}/udev/data/+drm:card@{int}-* r,   # For screen outputs
  @{run}/udev/data/+hid:* r,
  @{run}/udev/data/+input:input@{int} r,  # For mouse, keyboard, touchpad
  @{run}/udev/data/+leds:* r,
  @{run}/udev/data/+pci:* r,              # Identifies all PCI devices (CPU, GPU, Network, Disks, USB, etc.)
  @{run}/udev/data/+platform:* r,
  @{run}/udev/data/+rfkill:* r,
  @{run}/udev/data/+sound:card@{int} r,   # For sound card
  @{run}/udev/data/+thunderbolt:* r,
  @{run}/udev/data/c1:@{int} r,           # For RAM disk
  @{run}/udev/data/c6:@{int} r,           # For parallel printer devices /dev/lp*
  @{run}/udev/data/c10:@{int} r,          # For non-serial mice, misc features
  @{run}/udev/data/c13:@{int} r,          # For /dev/input/*
  @{run}/udev/data/c21:@{int} r,          # Generic SCSI access
  @{run}/udev/data/c29:@{int} r,          # For /dev/fb[0-9]*
  @{run}/udev/data/c81:@{int}  r,         # For video4linux
  @{run}/udev/data/c89:@{int}  r,         # For I2C bus interface
  @{run}/udev/data/c90:@{int} r,          # For RAM, ROM, Flash
  @{run}/udev/data/c99:@{int} r,          # For raw parallel ports /dev/parport*
  @{run}/udev/data/c108:@{int} r,         # For /dev/ppp
  @{run}/udev/data/c116:@{int} r,         # For ALSA
  @{run}/udev/data/c202:@{int} r,         # CPU model-specific registers
  @{run}/udev/data/c203:@{int} r,         # CPU CPUID information
  @{run}/udev/data/c226:@{int} r,         # For /dev/dri/card[0-9]*
  @{run}/udev/data/c@{dynamic}:@{int} r,  # For dynamic assignment range 234 to 254, 384 to 511
  @{run}/udev/data/n@{int} r,

  @{sys}/bus/[a-z]*/devices/ r,
  @{sys}/bus/pci/drivers_probe w,
  @{sys}/bus/pci/drivers/*/unbind w,
  @{sys}/class/[a-z]*/ r,
  @{sys}/devices/**/uevent r,
  @{sys}/devices/@{pci}/{class,revision,subsystem_vendor,subsystem_device} r,
  @{sys}/devices/@{pci}/{config,numa_node,device,vendor} r,
  @{sys}/devices/@{pci}/driver_override w,
  @{sys}/devices/@{pci}/mdev_supported_types/{,**} r,
  @{sys}/devices/@{pci}/mdev_supported_types/*/create w,
  @{sys}/devices/@{pci}/net/*/{,**} r,
  @{sys}/devices/@{pci}/remove w,
  @{sys}/devices/@{pci}/resource r,
  @{sys}/devices/@{pci}/sriov_totalvfs r,

  @{sys}/devices/system/cpu/cpu@{int}/cache/{,**} r,
  @{sys}/devices/system/cpu/cpu@{int}/topology/{,**} r,
  @{sys}/devices/system/cpu/isolated r,
  @{sys}/devices/system/cpu/present r,
  @{sys}/devices/system/node/ r,
  @{sys}/devices/system/node/node@{int}/ r,
  @{sys}/devices/system/node/node@{int}/{cpumap,distance,meminfo} r,
  @{sys}/devices/system/node/node@{int}/hugepages/{,**} r,
  @{sys}/devices/virtual/dmi/id/* r,
  @{sys}/devices/virtual/net/{,**} rw,

  @{sys}/kernel/debug/kvm/{,**} r,
  @{sys}/kernel/iommu_groups/ r,
  @{sys}/kernel/iommu_groups/@{int}/devices/ r,
  @{sys}/kernel/mm/hugepages/{,**} r,
  @{sys}/kernel/security/apparmor/profiles r,

  @{sys}/module/kvm_*/parameters/* r,
  @{sys}/module/vhost/parameters/max_mem_regions r,

  @{sys}/fs/cgroup/ r,
  @{sys}/fs/cgroup/cgroup.controllers r,
  @{sys}/fs/cgroup/machine.slice/* r,
  @{sys}/fs/cgroup/machine.slice/machine-qemu*.scope/{,**} rw,
  @{sys}/fs/cgroup/net_cls/machine.slice/ rw,
  @{sys}/fs/cgroup/net_cls/machine.slice/machine-qemu*.scope/{,**} rw,

        @{PROC}/@{pid}/cmdline r,
        @{PROC}/@{pid}/net/route r,
        @{PROC}/@{pids}/cgroup r,
        @{PROC}/@{pids}/net/dev r,
        @{PROC}/@{pids}/net/ip_tables_names r,
        @{PROC}/@{pids}/net/psched r,
        @{PROC}/@{pids}/stat r,
        @{PROC}/@{pids}/task/@{tid}/sched r,
        @{PROC}/@{pids}/task/@{tid}/schedstat r,
        @{PROC}/@{pids}/task/@{tid}/stat r,
        @{PROC}/devices r,
        @{PROC}/mtrr w,
        @{PROC}/sys/net/ipv{4,6}/** rw,
  owner @{PROC}/@{pid}/fd/ r,
  owner @{PROC}/@{pid}/mounts r,
  owner @{PROC}/@{pid}/task/@{tid}/comm rw,

  /dev/cpu/@{int}/msr r,
  /dev/dri/ r,
  /dev/hugepages/{,**} w,
  /dev/kvm rw,
  /dev/mapper/ r,
  /dev/mapper/control rw,
  /dev/net/tun rw,
  /dev/ptmx rw,
  /dev/shm/libvirt/{,**} rw,
  /dev/vfio/@{int} rwk,
  /dev/vhost-net rw,

  # Force the use of virt-aa-helper
  audit deny @{bin}/apparmor_parser rwxl,
  audit deny @{etc_rw}/apparmor.d/libvirt/** wxl,
  audit deny @{sys}/kernel/security/apparmor/features rwxl,
  audit deny @{sys}/kernel/security/apparmor/matching rwxl,
  audit deny @{sys}/kernel/security/apparmor/.* rwxl,

  profile qemu_bridge_helper {
    include <abstractions/base>

    capability net_admin,
    capability setgid,
    capability setpcap,
    capability setuid,

    network inet stream,

    # For communication/control from libvirtd
    unix (send, receive) type=stream addr=none peer=(label=libvirtd),
    signal (receive) set=(term) peer=libvirtd,

    /{usr/,}{lib,lib64,lib/qemu,libexec}/qemu-bridge-helper rmix,

    /etc/qemu/{,**} r,

    owner @{PROC}/@{pids}/status r,

    /dev/net/tun rw,
  }

  include if exists <usr/libvirtd>
  include if exists <local/libvirtd>
}

# vim:syntax=apparmor
