├── .gitignore ├── .gitlab-ci.yml ├── .qubesbuilder ├── Makefile ├── Makefile.builder ├── archlinux ├── PKGBUILD-initcpio-hook.sh ├── PKGBUILD-initcpio-install.sh ├── PKGBUILD-qubes-vm-kernel-support.install ├── PKGBUILD-qubes-vm-utils.install └── PKGBUILD.in ├── ci ├── codecov-keys.asc ├── codecov-wrapper └── requirements.txt ├── debian ├── changelog ├── compat ├── control ├── copyright ├── docs ├── libqubes-pure-dev.install ├── libqubes-pure0.install ├── libqubes-pure0.shlibs ├── libqubes-rpc-filecopy-dev.install ├── libqubes-rpc-filecopy2.install ├── libqubes-rpc-filecopy2.shlibs ├── python3-qubesimgconverter.install ├── qubes-kernel-vm-support.install ├── qubes-kernel-vm-support.postinst ├── qubes-kernel-vm-support.preinst ├── qubes-utils.install ├── qubes-utils.maintscript ├── rules └── source │ └── format ├── dracut ├── Makefile ├── full-dmroot │ ├── Makefile │ ├── module-setup.sh │ └── qubes_cow_setup.sh ├── full-modules │ ├── Makefile │ ├── module-setup.sh │ └── mount_modules.sh ├── simple │ ├── Makefile │ ├── init.sh │ └── module-setup.sh └── xen-balloon-scrub-pages │ ├── Makefile │ ├── module-setup.sh │ └── scrub_pages.sh ├── gptfixer ├── gpt.c ├── layout ├── test.sh ├── test.sh.stderr └── test.sh.stdout ├── grub ├── Makefile └── grub.qubes-kernel-vm-support ├── imgconverter ├── Makefile ├── qubesimgconverter │ ├── __init__.py │ ├── imggen.py │ ├── test.py │ └── test_integ.py └── setup.py ├── initramfs-tools ├── Makefile ├── local-top │ ├── qubes_cow_setup.sh │ └── scrub_pages.sh ├── qubes.conf └── qubes_vm ├── kernel-modules ├── Makefile ├── genfs.c └── qubes-prepare-vm-kernel ├── not-script ├── Makefile └── not-script.c ├── qmemman ├── Makefile ├── meminfo-writer.c ├── qubes-meminfo-writer-dom0.service └── qubes-meminfo-writer.service ├── qrexec-lib ├── Makefile ├── copy-file.c ├── crc32.c ├── crc32.h ├── ioall.c ├── ioall.h ├── libqubes-rpc-filecopy.h ├── pack.c ├── pure.h ├── qube-name.c ├── unicode-allowlist-table.c ├── unicode-generator.c ├── unicode.c ├── unpack.c └── validator-test.c ├── rpm_spec ├── qubes-kernel-vm-support.spec.in └── qubes-utils.spec.in ├── selinux ├── qubes-meminfo-writer.fc ├── qubes-meminfo-writer.if └── qubes-meminfo-writer.te ├── udev ├── Makefile ├── udev-block-add-change ├── udev-block-remove ├── udev-qubes-block.rules ├── udev-qubes-dmroot.rules ├── udev-qubes-misc.rules ├── udev-qubes-usb.rules ├── udev-usb-add-change ├── udev-usb-remove └── xen-devices-qubes.conf └── version /.gitignore: -------------------------------------------------------------------------------- 1 | rpm/ 2 | deb/ 3 | *~ 4 | *.o 5 | *.py[co] 6 | pkgs 7 | debian/changelog.* 8 | /selinux/tmp/ 9 | /selinux/*.pp 10 | /qrexec-lib/*.dep 11 | /qrexec-lib/unicode-class-table.c 12 | /qrexec-lib/unicode-generator 13 | /qrexec-lib/validator-test 14 | /qrexec-lib/libqubes-*.so* 15 | /imgconverter/qubesimgconverter.egg-info/ 16 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | checks:tests: 2 | after_script: 3 | - (cd qrexec-lib; gcov *.c || :) 4 | - (cd gptfixer; gcov *.c || :) 5 | - ./ci/codecov-wrapper --gcov 6 | before_script: 7 | - sudo dnf install -y python3-pip 'pkgconfig(icu-uc)' sequoia-sqv 8 | - export PATH="$PATH:$HOME/.local/bin" 9 | - pip3 install --quiet -r ci/requirements.txt 10 | - git config --global --add safe.directory "$PWD" 11 | script: 12 | - python3 -m coverage run -m unittest discover -v imgconverter -p test.py 13 | - export CFLAGS="--coverage -DCOVERAGE" LDFLAGS=--coverage 14 | - make -C qrexec-lib check NO_REBUILD_TABLE=1 15 | - gptfixer/test.sh 16 | stage: checks 17 | tags: 18 | - docker 19 | include: 20 | - file: /r4.3/gitlab-base.yml 21 | project: QubesOS/qubes-continuous-integration 22 | - file: /r4.3/gitlab-host.yml 23 | project: QubesOS/qubes-continuous-integration 24 | - file: /r4.3/gitlab-vm.yml 25 | project: QubesOS/qubes-continuous-integration 26 | -------------------------------------------------------------------------------- /.qubesbuilder: -------------------------------------------------------------------------------- 1 | host: 2 | rpm: 3 | build: 4 | - rpm_spec/qubes-utils.spec 5 | - rpm_spec/qubes-kernel-vm-support.spec 6 | vm: 7 | rpm: 8 | build: 9 | - rpm_spec/qubes-utils.spec 10 | - rpm_spec/qubes-kernel-vm-support.spec 11 | deb: 12 | build: 13 | - debian 14 | archlinux: 15 | build: 16 | - archlinux 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SBINDIR ?= /usr/sbin 2 | LIBDIR ?= /usr/lib64 3 | SCRIPTSDIR ?= /usr/lib/qubes 4 | SYSLIBDIR ?= /usr/lib 5 | INCLUDEDIR ?= /usr/include 6 | CFLAGS ?= -Wall -Wextra -Werror -O3 -g3 -Werror=format=2 7 | CC ?= gcc 8 | 9 | export LIBDIR SCRIPTSDIR SYSLIBDIR INCLUDEDIR 10 | .PHONY: all selinux install install-selinux install-fedora-kernel-support install-debian-kernel-support clean 11 | 12 | all: 13 | $(MAKE) -C qrexec-lib all 14 | $(MAKE) -C qmemman all 15 | $(MAKE) -C imgconverter all 16 | $(MAKE) -C not-script all 17 | selinux: 18 | $(MAKE) -f /usr/share/selinux/devel/Makefile -C selinux qubes-meminfo-writer.pp 19 | 20 | install: 21 | $(MAKE) -C udev install 22 | $(MAKE) -C qrexec-lib install 23 | $(MAKE) -C qmemman install 24 | $(MAKE) -C imgconverter install 25 | $(MAKE) -C not-script install 26 | 27 | install-selinux: 28 | install -m 0644 -D -t $(DESTDIR)/usr/share/selinux/packages selinux/qubes-meminfo-writer.pp 29 | install -m 0644 -D selinux/qubes-meminfo-writer.if $(DESTDIR)/usr/share/selinux/devel/include/contrib/ipp-qubes-meminfo-writer.if 30 | 31 | install-fedora-kernel-support: 32 | $(MAKE) -C dracut install 33 | $(MAKE) -C kernel-modules install 34 | $(MAKE) -C grub install-fedora 35 | 36 | install-debian-kernel-support: 37 | $(MAKE) -C initramfs-tools install 38 | $(MAKE) -C dracut install 39 | $(MAKE) -C grub install-debian 40 | 41 | install-gptfix: gptfixer/gpt 42 | install -D gptfixer/gpt $(DESTDIR)$(SBINDIR)/gptfix 43 | gptfixer/gpt_LDLIBS := -lz 44 | gptfixer/gpt_CFLAGS := -D_GNU_SOURCE -fno-strict-aliasing -fno-delete-null-pointer-checks -fno-strict-overflow 45 | %: %.c Makefile 46 | $(CC) $($(@)_CFLAGS) -o $@ $< $(CFLAGS) -MD -MP -MF $@.dep $($(@)_LDLIBS) 47 | -include gptfixer/*.dep 48 | 49 | clean: 50 | $(MAKE) -C qrexec-lib clean 51 | $(MAKE) -C qmemman clean 52 | $(MAKE) -C imgconverter clean 53 | $(MAKE) -C not-script clean 54 | rm -rf selinux/*.pp selinux/tmp/ 55 | rm -rf debian/changelog.* 56 | rm -rf pkgs 57 | -------------------------------------------------------------------------------- /Makefile.builder: -------------------------------------------------------------------------------- 1 | RPM_SPEC_FILES := rpm_spec/qubes-utils.spec rpm_spec/qubes-kernel-vm-support.spec 2 | ARCH_BUILD_DIRS := archlinux 3 | DEBIAN_BUILD_DIRS := debian 4 | 5 | # Support for new packaging 6 | ifneq ($(filter $(DISTRIBUTION), archlinux),) 7 | VERSION := $(file <$(ORIG_SRC)/$(DIST_SRC)/version) 8 | GIT_TARBALL_NAME ?= qubes-vm-utils-$(VERSION)-1.tar.gz 9 | SOURCE_COPY_IN := source-archlinux-copy-in 10 | 11 | source-archlinux-copy-in: PKGBUILD = $(CHROOT_DIR)/$(DIST_SRC)/$(ARCH_BUILD_DIRS)/PKGBUILD 12 | source-archlinux-copy-in: 13 | cp $(PKGBUILD).in $(CHROOT_DIR)/$(DIST_SRC)/PKGBUILD 14 | sed -i "s/@VERSION@/$(VERSION)/g" $(CHROOT_DIR)/$(DIST_SRC)/PKGBUILD 15 | sed -i "s/@REL@/1/g" $(CHROOT_DIR)/$(DIST_SRC)/PKGBUILD 16 | endif 17 | -------------------------------------------------------------------------------- /archlinux/PKGBUILD-initcpio-hook.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ash 2 | 3 | run_earlyhook() { 4 | 5 | msg "Starting Qubes copy on write setup script" 6 | 7 | /usr/lib/qubes/scrub_pages.sh 8 | /usr/lib/qubes/qubes_cow_setup.sh 9 | 10 | } 11 | -------------------------------------------------------------------------------- /archlinux/PKGBUILD-initcpio-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | build() { 4 | 5 | add_module "xen-blkfront" 6 | add_binary "/usr/bin/sfdisk" 7 | add_binary "/usr/bin/mkswap" 8 | add_binary "/usr/bin/swapon" 9 | add_binary "/usr/bin/dmsetup" 10 | add_binary "/usr/bin/gptfix" 11 | add_binary "/usr/lib/qubes/scrub_pages.sh" 12 | add_binary "/usr/lib/qubes/qubes_cow_setup.sh" 13 | 14 | map add_module \ 15 | 'dm-mod' \ 16 | 'dm-snapshot' 17 | 18 | add_runscript 19 | 20 | # Mark it's safe to add scrub_pages=0 to the kernel cmdline now 21 | echo 1 > /var/lib/qubes/initramfs-updated 22 | } 23 | 24 | help() { 25 | cat < /boot/grub/grub.cfg)" 6 | echo "3/ Reinstall qubes-vm-kernel-support to ensure Qubes-OS kernel module is compiled and that the initcpio is rebuilt" 7 | echo "This should then be handled automatically in your next kernel updates" 8 | } 9 | 10 | ## arg 1: the new package version 11 | post_install () { 12 | local begin='### BEGIN QUBES HOOKS ###' end='### END QUBES HOOKS ###' 13 | local combined="\\ 14 | $begin\\ 15 | # Set the kernel command line to what the QubesOS initramfs hook expects.\\ 16 | # If the kernel command line is wrong, the system will not boot.\\ 17 | \\ 18 | . /etc/default/grub.qubes\\ 19 | . /etc/default/grub.qubes-kernel-vm-support\\ 20 | $end" 21 | echo "Adding qubes required hooks to mkinitcpio.conf" 22 | if [[ ! -s /etc/default/grub ]]; then 23 | echo >> /etc/default/grub 24 | fi 25 | sed -Ei '/^HOOKS=/ s/(block)/\1 qubes/' /etc/mkinitcpio.conf 26 | echo 'Adding qubes required hooks to /etc/default/grub' 27 | sed -Ei "/^$begin\$/,/^$end\$/{ 28 | \$c$combined 29 | d 30 | } 31 | \$a$combined 32 | " /etc/default/grub 33 | grub-mkconfig -o /boot/grub/grub.cfg 34 | help 35 | } 36 | 37 | post_upgrade () { 38 | post_install 39 | } 40 | 41 | post_remove () { 42 | local begin='### BEGIN QUBES HOOKS ###' end='### END QUBES HOOKS ###' 43 | echo 'Removing qubes required hooks from mkinitcpio.conf' 44 | sed -Ei '/^HOOKS=/ s/(block) (lvm2 )?qubes/\1/' /etc/mkinitcpio.conf 45 | echo 'Removing qubes required hooks from /etc/default/grub' 46 | sed -Ei "/^$begin\$/,/^$end\$/d" /etc/default/grub 47 | grub-mkconfig -o /boot/grub/grub.cfg 48 | } 49 | -------------------------------------------------------------------------------- /archlinux/PKGBUILD-qubes-vm-utils.install: -------------------------------------------------------------------------------- 1 | 2 | ## arg 1: the new package version 3 | post_install() { 4 | ldconfig 5 | /bin/systemctl enable qubes-meminfo-writer-dom0.service > /dev/null 2>&1 6 | /bin/systemctl enable qubes-meminfo-writer.service > /dev/null 2>&1 7 | } 8 | 9 | post_upgrade() { 10 | ldconfig 11 | } 12 | 13 | post_remove() { 14 | ldconfig 15 | /bin/systemctl disable qubes-meminfo-writer.service > /dev/null 2>&1 16 | /bin/systemctl disable qubes-meminfo-writer.service > /dev/null 2>&1 17 | } 18 | 19 | -------------------------------------------------------------------------------- /archlinux/PKGBUILD.in: -------------------------------------------------------------------------------- 1 | # Maintainer: Frédéric Pierret (fepitre) 2 | 3 | pkgname=(qubes-vm-utils qubes-vm-kernel-support) 4 | pkgver=@VERSION@ 5 | pkgrel=@REL@ 6 | pkgdesc="Common Linux files for Qubes VM." 7 | arch=("x86_64") 8 | url="http://qubes-os.org/" 9 | license=('GPL') 10 | depends=( 11 | gcc 12 | make 13 | pkgconfig 14 | python-setuptools 15 | icu 16 | qubes-libvchan-xen 17 | ) 18 | options=('staticlibs') 19 | _pkgnvr="${pkgname}-${pkgver}-${pkgrel}" 20 | source=("${_pkgnvr}.tar.gz") 21 | sha256sums=(SKIP) 22 | 23 | build() { 24 | cd "${_pkgnvr}" 25 | make all 26 | } 27 | 28 | package_qubes-vm-utils() { 29 | cd "${_pkgnvr}" 30 | 31 | depends=( 32 | graphicsmagick 33 | python-cairo 34 | python-pillow 35 | python-numpy 36 | icu 37 | ) 38 | install=archlinux/PKGBUILD-qubes-vm-utils.install 39 | 40 | make install \ 41 | DESTDIR="$pkgdir" \ 42 | LIBDIR=/usr/lib \ 43 | SYSLIBDIR=/usr/lib \ 44 | SBINDIR=/usr/bin 45 | } 46 | 47 | package_qubes-vm-kernel-support() { 48 | cd "${_pkgnvr}" 49 | 50 | depends=( 51 | mkinitcpio 52 | grub 53 | ) 54 | install=archlinux/PKGBUILD-qubes-vm-kernel-support.install 55 | 56 | mkdir -p "${pkgdir}/usr/lib/initcpio/install/" 57 | mkdir -p "${pkgdir}/usr/lib/initcpio/hooks/" 58 | mkdir -p "${pkgdir}/usr/lib/qubes/" 59 | mkdir -p "${pkgdir}/etc/default" 60 | 61 | install -m 611 "${srcdir}/${_pkgnvr}/archlinux/PKGBUILD-initcpio-install.sh" "${pkgdir}/usr/lib/initcpio/install/qubes" 62 | install -m 611 "${srcdir}/${_pkgnvr}/archlinux/PKGBUILD-initcpio-hook.sh" "${pkgdir}/usr/lib/initcpio/hooks/qubes" 63 | install -m 755 "${srcdir}/${_pkgnvr}/dracut/full-dmroot/qubes_cow_setup.sh" "${pkgdir}/usr/lib/qubes/qubes_cow_setup.sh" 64 | install -m 755 "${srcdir}/${_pkgnvr}/dracut/xen-balloon-scrub-pages/scrub_pages.sh" "${pkgdir}/usr/lib/qubes/scrub_pages.sh" 65 | install -m 0644 "${srcdir}/${_pkgnvr}/grub/grub.qubes-kernel-vm-support" "${pkgdir}/etc/default/grub.qubes-kernel-vm-support" 66 | make install-gptfix SBINDIR=/usr/bin "DESTDIR=$pkgdir" 67 | } 68 | -------------------------------------------------------------------------------- /ci/codecov-keys.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQINBGCsMn0BEACiCKZOhkbhUjb+obvhH49p3ShjJzU5b/GqAXSDhRhdXUq7ZoGq 4 | KEKCd7sQHrCf16Pi5UVacGIyE9hS93HwY15kMlLwM+lNeAeCglEscOjpCly1qUIr 5 | sN1wjkd2cwDXS6zHBJTqJ7wSOiXbZfTAeKhd6DuLEpmA+Rz4Yc+4qZP+fVxVG3Pv 6 | 2v06m+E5CP/JQVQPO8HYi+S36hJImTh+zaDspu+VujSai5KzJ6YKmgwslVNIp5X5 7 | GnEr2uAh5w6UTnt9UQUjFFliAvQ3lPLWzm7DWs6AP9hslYxSWzwbzVF5qbOIjUJL 8 | KfoUpvCYDs2ObgRn8WUQO0ndkRCBIxhlF3HGGYWKQaCEsiom7lyi8VbAszmUCDjw 9 | HdbQHFmm5yHLpTXJbg+iaxQzKnhWVXzye5/x92IJmJswW81Ky346VxYdC1XFL/+Y 10 | zBaj9oMmV7WfRpdch09Gf4TgosMzWf3NjJbtKE5xkaghJckIgxwzcrRmF/RmCJue 11 | IMqZ8A5qUUlK7NBzj51xmAQ4BtkUa2bcCBRV/vP+rk9wcBWz2LiaW+7Mwlfr/C/Q 12 | Swvv/JW2LsQ4iWc1BY7m7ksn9dcdypEq/1JbIzVLCRDG7pbMj9yLgYmhe5TtjOM3 13 | ygk25584EhXSgUA3MZw+DIqhbHQBYgrKndTr2N/wuBQY62zZg1YGQByD4QARAQAB 14 | tEpDb2RlY292IFVwbG9hZGVyIChDb2RlY292IFVwbG9hZGVyIFZlcmlmaWNhdGlv 15 | biBLZXkpIDxzZWN1cml0eUBjb2RlY292LmlvPokCTgQTAQoAOBYhBCcDTn/bhQ4L 16 | vCxi/4Brsortd5hpBQJgrDJ9AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJ 17 | EIBrsortd5hpxLMP/3Fbgx5EG7zUUOqPZ+Ya9z8JlZFIkh3FxYMfMFE8jH9Es26F 18 | V2ZTJLO259MxM+5N0XzObi3h4XqIzBn42pDRfwtojY5wl2STJ9Bzu+ykPog7OB1u 19 | yfWXDRKcqPTUIxI1/WdU+c0/WNE6wjyzK+lRc1YUlp4pdNU7l+j2vKN+jGi2b6nV 20 | PTPRsMcwy3B90fKf5h2wNMNqO+KX/rjgpG9Uhej+xyFWkGM1tZDQQYFj+ugQUj61 21 | BMsQrUmxOnaVVnix21cHnACDCaxqgQZH3iZyEOKPNMsRFRP+0fLEnUMP+DVnQE6J 22 | Brk1Z+XhtjGI9PISQVx5KKDKscreS/D5ae2Cw/FUlQMf57kir6mkbZVhz2khtccz 23 | atD0r59WomNywIDyk1QfAKV0+O0WeJg8A69/Jk6yegsrUb5qEfkih/I38vvI0OVL 24 | BYve/mQIHuQo5ziBptNytCrN5TXHXzguX9GOW1V1+3DR+w/vXcnz67sjlYDysf1f 25 | JUZv9edZ2RGKW7agbrgOw2hB+zuWZ10tjoEcsaSGOLtKRGFDfmu/dBxzl8yopUpa 26 | Tn79QKOieleRm5+uCcKCPTeKV0GbhDntCZJ+Yiw6ZPmrpcjDowAoMQ9kiMVa10+Q 27 | WwwoaRWuqhf+dL6Q2OLFOxlyCDKVSyW0YF4Vrf3fKGyxKJmszAL+NS1mVcdxuQIN 28 | BGCsMn0BEADLrIesbpfdAfWRvUFDN+PoRfa0ROwa/JOMhEgVsowQuk9No8yRva/X 29 | VyiA6oCq6na7IvZXMxT7di4FWDjDtw5xHjbtFg336IJTGBcnzm7WIsjvyyw8kKfB 30 | 8cvG7D2OkzAUF8SVXLarJ1zdBP/Dr1Nz6F/gJsx5+BM8wGHEz4DsdMRV7ZMTVh6b 31 | PaGuPZysPjSEw62R8MFJ1fSyDGCKJYwMQ/sKFzseNaY/kZVR5lq0dmhiYjNVQeG9 32 | HJ6ZCGSGT5PKNOwx/UEkT6jhvzWgfr2eFVGJTcdwSLEgIrJIDzP7myHGxuOiuCmJ 33 | ENgL1f7mzGkJ/hYXq1RWqsn1Fh2I9KZMHggqu4a+s3RiscmNcbIlIhJLXoE1bxZ/ 34 | TfYZ9Aod6Bd5TsSMTZNwV2am9zelhDiFF60FWww/5nEbhm/X4suC9W86qWBxs3Kh 35 | vk1dxhElRjtgwUEHA5OFOO48ERHfR7COH719D/YmqLU3EybBgJbGoC/yjlGJxv0R 36 | kOMAiG2FneNKEZZihReh8A5Jt6jYrSoHFRwL6oJIZfLezB7Rdajx1uH7uYcUyIaE 37 | SiDWlkDw/IFM315NYFA8c1TCSIfnabUYaAxSLNFRmXnt+GQpm44qAK1x8EGhY633 38 | e5B4FWorIXx0tTmsVM4rkQ6IgAodeywKG+c2Ikd+5dQLFmb7dW/6CwARAQABiQI2 39 | BBgBCgAgFiEEJwNOf9uFDgu8LGL/gGuyiu13mGkFAmCsMn0CGwwACgkQgGuyiu13 40 | mGkYWxAAkzF64SVpYvY9nY/QSYikL8UHlyyqirs6eFZ3Mj9lMRpHM2Spn9a3c701 41 | 0Ge4wDbRP2oftCyPP+p9pdUA77ifMTlRcoMYX8oXAuyE5RT2emBDiWvSR6hQQ8bZ 42 | WFNXal+bUPpaRiruCCUPD2b8Od1ftzLqbYOosxr/m5Du0uahgOuGw6zlGBJCVOo7 43 | UB2Y++oZ8P7oDGF722opepWQ+bl2a6TRMLNWWlj4UANknyjlhyZZ7PKhWLjoC6MU 44 | dAKcwQUdp+XYLc/3b00bvgju0e99QgHZMX2fN3d3ktdN5Q2fqiAi5R6BmCCO4ISF 45 | o5j10gGU/sdqGHvNhv5C21ibun7HEzMtxBhnhGmytfBJzrsj7GOReePsfTLoCoUq 46 | dFMOAVUDciVfRtL2m8cv42ZJOXtPfDjsFOf8AKJk40/tc8mMMqZP7RVBr9RWOoq5 47 | y9D37NfI6UB8rPZ6qs0a1Vfm8lIh2/k1AFECduXgftMDTsmmXOgXXS37HukGW7AL 48 | QKWiWJQF/XopkXwkyAYpyuyRMZ77oF7nuqLFnl5VVEiRo0Fwu45erebc6ccSwYZU 49 | 8pmeSx7s0aJtxCZPSZEKZ3mn0BXOR32Cgs48CjzFWf6PKucTwOy/YO0/4Gt/upNJ 50 | 3DyeINcYcKyD08DEIF9f5tLyoiD4xz+N23ltTBoMPyv4f3X/wCQ= 51 | =ch7z 52 | -----END PGP PUBLIC KEY BLOCK----- 53 | -------------------------------------------------------------------------------- /ci/codecov-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | curl -Os https://uploader.codecov.io/latest/linux/codecov 6 | curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM 7 | curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig 8 | 9 | sqv --keyring ci/codecov-keys.asc codecov.SHA256SUM.sig codecov.SHA256SUM 10 | shasum -a 256 -c codecov.SHA256SUM 11 | 12 | chmod +x codecov 13 | 14 | python3 -m coverage xml || : 15 | 16 | if [[ "$CI_COMMIT_BRANCH" =~ ^pr- ]]; then 17 | PR=${CI_COMMIT_BRANCH#pr-} 18 | parents=$(git show -s --format='%P %ae') 19 | if [ $(wc -w <<<"$parents") -eq 3 ] && [ "${parents##* }" = "fepitre-bot@qubes-os.org" ]; then 20 | commit_sha=$(cut -f 2 -d ' ' <<<"${parents}") 21 | else 22 | commit_sha=$(git show -s --format='%H') 23 | fi 24 | exec ./codecov --pr "$PR" --sha "$commit_sha" "$@" 25 | fi 26 | exec ./codecov "$@" 27 | -------------------------------------------------------------------------------- /ci/requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow 2 | numpy 3 | coverage 4 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: qubes-utils 2 | Section: admin 3 | Priority: extra 4 | Maintainer: Davíð Steinn Geirsson 5 | Build-Depends: 6 | debhelper, 7 | libxen-dev, 8 | pkg-config, 9 | python3-setuptools, 10 | libicu-dev, 11 | libz-dev, 12 | Standards-Version: 4.4.0.1 13 | Homepage: https://www.qubes-os.org 14 | Vcs-Git: https://github.com/QubesOS/qubes-linux-utils.git 15 | Vcs-Browser: https://github.com/QubesOS/qubes-linux-utils 16 | 17 | Package: qubes-utils 18 | Architecture: any 19 | Depends: lsb-base, ${shlibs:Depends}, ${misc:Depends} 20 | Conflicts: qubes-linux-utils 21 | Breaks: qubes-core-agent (<< 3.1.4) 22 | Recommends: python3 23 | Description: Qubes Linux utilities 24 | This package includes the basic qubes utilities necessary for domU. 25 | 26 | Package: qubes-kernel-vm-support 27 | Architecture: any 28 | Depends: 29 | busybox, 30 | initramfs-tools | dracut, 31 | grub2-common, 32 | ${misc:Depends} 33 | Description: Qubes VM kernel and initramfs modules 34 | This package contains: 35 | 1. mkinitramfs module required to setup Qubes VM root filesystem. This package 36 | is needed in VM only when the VM uses its own kernel (via pvgrub or so). 37 | Otherwise initrd is provided by dom0. 38 | 39 | Package: libqubes-rpc-filecopy2 40 | Architecture: any 41 | Depends: ${shlibs:Depends}, ${misc:Depends} 42 | Breaks: qubes-utils (<< 3.1.4) 43 | Replaces: qubes-utils (<< 3.1.4) 44 | Description: Qubes file copy protocol library 45 | This library can be used for both sending files using qfile protocol and for 46 | receiving them. 47 | 48 | Package: libqubes-rpc-filecopy-dev 49 | Architecture: any 50 | Section: libdevel 51 | Depends: libqubes-rpc-filecopy2 (= ${binary:Version}), ${misc:Depends} 52 | Breaks: qubes-utils (<< 3.1.4) 53 | Replaces: qubes-utils (<< 3.1.4) 54 | Description: Development headers for libqrexec-utils 55 | This package contains files required to compile Qubes file copy related 56 | utilities like qfile-agent. 57 | 58 | Package: libqubes-pure0 59 | Architecture: any 60 | Depends: ${shlibs:Depends}, ${misc:Depends} 61 | Breaks: libqubes-rpc-filecopy2 (<< 4.2.12) 62 | Replaces: libqubes-rpc-filecopy2 (<< 4.2.12) 63 | Description: Qubes file copy protocol library 64 | Utility library with qubes-specific functions. 65 | 66 | Package: libqubes-pure-dev 67 | Architecture: any 68 | Section: libdevel 69 | Depends: libqubes-pure0 (= ${binary:Version}), ${misc:Depends} 70 | Breaks: libqubes-rpc-filecopy-dev (<< 4.2.12) 71 | Replaces: libqubes-rpc-filecopy-dev (<< 4.2.12) 72 | Description: Development headers for libqrexec-utils 73 | Utility library with qubes-specific functions - headers 74 | 75 | Package: python3-qubesimgconverter 76 | Architecture: any 77 | Depends: python3-cairo, python3-pil, python3-numpy, ${misc:Depends} 78 | Breaks: qubes-utils (<< 4.1.9) 79 | Replaces: qubes-utils (<< 4.1.9) 80 | Description: Python package qubesimgconverter 81 | Python package qubesimgconverter 82 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: qubes-linux-utils 3 | Source: 4 | 5 | Files: * 6 | Copyright: ?-2014 Qubes Developers 7 | License: GPL-2+ 8 | This package is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | . 13 | This package is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | . 18 | You should have received a copy of the GNU General Public License 19 | along with this program. If not, see 20 | . 21 | On Debian systems, the complete text of the GNU General 22 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 23 | 24 | Files: debian/* 25 | Copyright: 2014 Davíð Steinn Geirsson 26 | License: GPL-2+ 27 | This package is free software; you can redistribute it and/or modify 28 | it under the terms of the GNU General Public License as published by 29 | the Free Software Foundation; either version 2 of the License, or 30 | (at your option) any later version. 31 | . 32 | This package is distributed in the hope that it will be useful, 33 | but WITHOUT ANY WARRANTY; without even the implied warranty of 34 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 35 | GNU General Public License for more details. 36 | . 37 | You should have received a copy of the GNU General Public License 38 | along with this program. If not, see 39 | . 40 | On Debian systems, the complete text of the GNU General 41 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 42 | 43 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QubesOS/qubes-linux-utils/673e1e9f5b285309f399a8901a57d3466b98c8bd/debian/docs -------------------------------------------------------------------------------- /debian/libqubes-pure-dev.install: -------------------------------------------------------------------------------- 1 | usr/include/qubes/pure.h 2 | usr/lib/libqubes-pure.so 3 | -------------------------------------------------------------------------------- /debian/libqubes-pure0.install: -------------------------------------------------------------------------------- 1 | usr/lib/libqubes-pure.so.0* 2 | -------------------------------------------------------------------------------- /debian/libqubes-pure0.shlibs: -------------------------------------------------------------------------------- 1 | libqubes-pure 0 libqubes-pure0 (>= 4.3.2) 2 | -------------------------------------------------------------------------------- /debian/libqubes-rpc-filecopy-dev.install: -------------------------------------------------------------------------------- 1 | usr/include/libqubes-rpc-filecopy.h 2 | usr/lib/libqubes-rpc-filecopy.so 3 | -------------------------------------------------------------------------------- /debian/libqubes-rpc-filecopy2.install: -------------------------------------------------------------------------------- 1 | usr/lib/libqubes-rpc-filecopy.so.2* 2 | -------------------------------------------------------------------------------- /debian/libqubes-rpc-filecopy2.shlibs: -------------------------------------------------------------------------------- 1 | libqubes-rpc-filecopy 2 libqubes-rpc-filecopy2 (>= 4.3.2) 2 | -------------------------------------------------------------------------------- /debian/python3-qubesimgconverter.install: -------------------------------------------------------------------------------- 1 | usr/lib/python3/dist-packages/qubesimgconverter*/* -------------------------------------------------------------------------------- /debian/qubes-kernel-vm-support.install: -------------------------------------------------------------------------------- 1 | usr/share/initramfs-tools/conf.d/qubes.conf 2 | usr/share/initramfs-tools/scripts/local-top/scrub_pages 3 | usr/share/initramfs-tools/scripts/local-top/qubes_cow_setup 4 | usr/share/initramfs-tools/hooks/qubes_vm 5 | usr/lib/dracut/modules.d/90qubes-vm/* 6 | usr/lib/dracut/modules.d/90qubes-vm-modules/* 7 | usr/lib/dracut/modules.d/90qubes-vm-simple/* 8 | usr/lib/dracut/modules.d/80xen-scrub-pages/* 9 | etc/default/grub.d/30-qubes-kernel-vm-support.cfg 10 | usr/sbin/gptfix 11 | -------------------------------------------------------------------------------- /debian/qubes-kernel-vm-support.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # postinst script for qubes-kernel-vm-support 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # The postinst script may be called in the following ways: 9 | # * 'configure' 10 | # * 'abort-upgrade' 11 | # * 'abort-remove' 'in-favour' 12 | # 13 | # * 'abort-remove' 14 | # * 'abort-deconfigure' 'in-favour' 15 | # 'removing' 16 | # 17 | # 18 | # For details, see http://www.debian.org/doc/debian-policy/ or 19 | # https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html or 20 | # the debian-policy package 21 | 22 | 23 | case "${1}" in 24 | configure) 25 | if [ -x /usr/sbin/update-initramfs ]; then 26 | if update-initramfs -u && [ -d /var/lib/qubes ]; then 27 | # "milestone" initramfs update version: 28 | # 1 - addition of xen scrub_pages enabling code 29 | echo 1 > /var/lib/qubes/initramfs-updated 30 | fi 31 | fi 32 | ;; 33 | 34 | abort-upgrade|abort-remove|abort-deconfigure) 35 | exit 0 36 | ;; 37 | 38 | *) 39 | echo "postinst called with unknown argument \`${1}'" >&2 40 | exit 1 41 | ;; 42 | esac 43 | 44 | # dh_installdeb will replace this with shell code automatically 45 | # generated by other debhelper scripts. 46 | 47 | #DEBHELPER# 48 | 49 | ## https://phabricator.whonix.org/T377 50 | ## Debian has no update-grub trigger yet: 51 | ## https://bugs.debian.org/481542 52 | 53 | if command -v update-grub >/dev/null 2>&1; then 54 | update-grub || \ 55 | echo "$DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME ERROR: Running \ 56 | 'update-grub' failed with exit code $?. $DPKG_MAINTSCRIPT_PACKAGE is most \ 57 | likely only the trigger, not the cause. Unless you know this is not an issue, \ 58 | you should fix running 'update-grub', otherwise your system might no longer \ 59 | boot." >&2 60 | fi 61 | 62 | exit 0 63 | 64 | # vim: set ts=4 sw=4 sts=4 et : 65 | -------------------------------------------------------------------------------- /debian/qubes-kernel-vm-support.preinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | mkdir --parents /boot/grub || true 6 | 7 | #DEBHELPER# 8 | 9 | exit 0 10 | -------------------------------------------------------------------------------- /debian/qubes-utils.install: -------------------------------------------------------------------------------- 1 | usr/bin/meminfo-writer 2 | lib/systemd/system/qubes-meminfo-writer.service 3 | usr/lib/qubes/* 4 | usr/lib/udev/* 5 | usr/lib/tmpfiles.d/xen-devices-qubes.conf 6 | etc/xen/scripts/qubes-block 7 | -------------------------------------------------------------------------------- /debian/qubes-utils.maintscript: -------------------------------------------------------------------------------- 1 | rm_conffile /etc/udev/rules.d/99-qubes-block.rules 3.1.6~ 2 | rm_conffile /etc/udev/rules.d/99-qubes-usb.rules 3.1.6~ 3 | rm_conffile /etc/udev/rules.d/99-qubes-misc.rules 3.1.6~ 4 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | export DESTDIR=$(shell pwd)/debian/tmp 5 | 6 | # Uncomment this to turn on verbose mode. 7 | #export DH_VERBOSE=1 8 | 9 | %: 10 | dh $@ --with=systemd 11 | 12 | override_dh_auto_build: 13 | make all LIBDIR=/usr/lib DEBIANBUILD=1 14 | 15 | override_dh_auto_install: 16 | make install LIBDIR=/usr/lib DEBIANBUILD=1 PYTHON_PREFIX_ARG=--install-layout=deb 17 | make install-debian-kernel-support LIBDIR=/usr/lib DEBIANBUILD=1 18 | make install-gptfix SBINDIR=/usr/sbin 19 | 20 | override_dh_install: 21 | dh_install --fail-missing 22 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /dracut/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | $(MAKE) -C simple 3 | $(MAKE) -C full-dmroot 4 | $(MAKE) -C full-modules 5 | $(MAKE) -C xen-balloon-scrub-pages 6 | -------------------------------------------------------------------------------- /dracut/full-dmroot/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | install -d $(DESTDIR)/usr/lib/dracut/modules.d/90qubes-vm 3 | install module-setup.sh qubes_cow_setup.sh \ 4 | $(DESTDIR)/usr/lib/dracut/modules.d/90qubes-vm/ 5 | -------------------------------------------------------------------------------- /dracut/full-dmroot/module-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check() { 4 | if [ -f /usr/share/qubes/marker-vm ]; then 5 | return 0 6 | else 7 | return 255 8 | fi 9 | } 10 | 11 | depends() { 12 | echo dm 13 | return 0 14 | } 15 | 16 | install() { 17 | inst_hook pre-trigger 90 $moddir/qubes_cow_setup.sh 18 | inst_multiple \ 19 | sfdisk \ 20 | swapon \ 21 | mkswap \ 22 | gptfix 23 | } 24 | -------------------------------------------------------------------------------- /dracut/full-dmroot/qubes_cow_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This file should be placed in pre-trigger directory in dracut's initramfs, or 4 | # scripts/local-top in case of initramfs-tools 5 | # 6 | 7 | # initramfs-tools (Debian) API 8 | PREREQS="" 9 | case "$1" in 10 | prereqs) 11 | # This runs during initramfs creation 12 | echo "$PREREQS" 13 | exit 0 14 | ;; 15 | esac 16 | 17 | # This runs inside real initramfs 18 | if [ -r /scripts/functions ]; then 19 | # We're running in Debian's initramfs 20 | . /scripts/functions 21 | alias die=panic 22 | alias info=true 23 | alias warn=log_warning_msg 24 | alias log_begin=log_begin_msg 25 | alias log_end=log_end_msg 26 | elif [ -r /lib/dracut-lib.sh ]; then 27 | . /lib/dracut-lib.sh 28 | alias log_begin=info 29 | alias log_end=true 30 | else 31 | die() { 32 | echo "$@" 33 | exit 1 34 | } 35 | alias info=echo 36 | alias warn=echo 37 | alias log_begin=echo 38 | alias log_end=true 39 | fi 40 | 41 | 42 | info "Qubes initramfs script here:" 43 | 44 | if ! grep -q 'root=[^ ]*dmroot' /proc/cmdline; then 45 | warn "dmroot not requested, probably not a Qubes VM" 46 | exit 0 47 | fi 48 | 49 | if [ -e /dev/mapper/dmroot ] ; then 50 | die "Qubes: FATAL error: /dev/mapper/dmroot already exists?!" 51 | fi 52 | 53 | modprobe xenblk || modprobe xen-blkfront || warn "Qubes: Cannot load Xen Block Frontend..." 54 | 55 | log_begin "Waiting for /dev/xvda* devices..." 56 | udevadm settle --exit-if-exists=/dev/xvda 57 | 58 | # prefer partition if exists 59 | if /usr/sbin/gptfix fix /dev/xvda; then 60 | udevadm settle --exit-if-exists=/dev/xvda1 61 | if [ -e "/dev/disk/by-partlabel/Root\\x20filesystem" ]; then 62 | ROOT_DEV=$(readlink "/dev/disk/by-partlabel/Root\\x20filesystem") 63 | ROOT_DEV=${ROOT_DEV##*/} 64 | else 65 | ROOT_DEV=xvda3 66 | fi 67 | if ! [ -b "/dev/$ROOT_DEV" ]; then udevadm settle "--exit-if-exists=/dev/$ROOT_DEV"; fi 68 | else 69 | status=$? 70 | case $status in 71 | (1|2) # EIO, ENOMEM, or bug. Fatal. 72 | die 'Fatal error reading partition table';; 73 | (4|5|8) # Bad or no partition table 74 | ROOT_DEV=xvda;; 75 | (*) 76 | # TODO: what should be done? 77 | # This is things like: 78 | # - "Partition table not supported" 79 | die 'GPT cannot be fixed';; 80 | esac 81 | fi 82 | log_end 83 | 84 | SWAP_SIZE_GiB=1 85 | SWAP_SIZE_512B=$(( SWAP_SIZE_GiB * 1024 * 1024 * 2 )) 86 | 87 | if [ `cat /sys/class/block/$ROOT_DEV/ro` = 1 ] ; then 88 | log_begin "Qubes: Doing COW setup for AppVM..." 89 | 90 | while ! [ -e /dev/xvdc ]; do sleep 0.1; done 91 | VOLATILE_SIZE_512B=$(cat /sys/class/block/xvdc/size) 92 | if [ $VOLATILE_SIZE_512B -lt $SWAP_SIZE_512B ]; then 93 | die "volatile.img smaller than $SWAP_SIZE_GiB GiB, cannot continue" 94 | fi 95 | sfdisk -q /dev/xvdc >/dev/null </dev/null <> \ 130 | /etc/udev/rules.d/99-root.rules 131 | udevadm control -R 132 | udevadm trigger 133 | log_end 134 | fi 135 | -------------------------------------------------------------------------------- /dracut/full-modules/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | install -d $(DESTDIR)/usr/lib/dracut/modules.d/90qubes-vm-modules 3 | install module-setup.sh mount_modules.sh \ 4 | $(DESTDIR)/usr/lib/dracut/modules.d/90qubes-vm-modules/ 5 | -------------------------------------------------------------------------------- /dracut/full-modules/module-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check() { 4 | return 255 5 | } 6 | 7 | install() { 8 | inst_hook pre-pivot 50 $moddir/mount_modules.sh 9 | } 10 | -------------------------------------------------------------------------------- /dracut/full-modules/mount_modules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This file should be places in pre-pivot directory in dracut's initramfs 4 | # 5 | 6 | 7 | kver="`uname -r`" 8 | if ! [ -d "$NEWROOT/lib/modules/$kver/kernel" ]; then 9 | echo "Waiting for /dev/xvdd device..." 10 | while ! [ -e /dev/xvdd ]; do sleep 0.1; done 11 | 12 | # Mount only `uname -r` subdirectory, to leave the rest of /lib/modules writable 13 | mkdir -p /tmp/modules 14 | mount -n -t ext3 /dev/xvdd /tmp/modules 15 | if ! [ -d "$NEWROOT/lib/modules/$kver" ]; then 16 | mount "$NEWROOT" -o remount,rw 17 | mkdir -p "$NEWROOT/lib/modules/$kver" 18 | mount "$NEWROOT" -o remount,ro 19 | fi 20 | mount --bind "/tmp/modules/$kver" "$NEWROOT/lib/modules/$kver" 21 | umount /tmp/modules 22 | rmdir /tmp/modules 23 | fi 24 | 25 | killall udevd systemd-udevd 26 | -------------------------------------------------------------------------------- /dracut/simple/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | install -d $(DESTDIR)/usr/lib/dracut/modules.d/90qubes-vm-simple 3 | install module-setup.sh init.sh \ 4 | $(DESTDIR)/usr/lib/dracut/modules.d/90qubes-vm-simple/ 5 | # flag indicating the module will (re-)enable scrub-pages option 6 | touch $(DESTDIR)/usr/lib/dracut/modules.d/90qubes-vm-simple/xen-scrub-pages-supported 7 | -------------------------------------------------------------------------------- /dracut/simple/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Qubes initramfs script here:" 3 | 4 | mkdir -p /proc /sys /dev 5 | mount -t proc proc /proc 6 | mount -t sysfs sysfs /sys 7 | mount -t devtmpfs devtmpfs /dev 8 | 9 | if [ -w /sys/devices/system/xen_memory/xen_memory0/scrub_pages ]; then 10 | # re-enable xen-balloon pages scrubbing, after initial balloon down 11 | echo 1 > /sys/devices/system/xen_memory/xen_memory0/scrub_pages 12 | fi 13 | 14 | # If device-mapper is built-in then Linux creates /dev/mapper, 15 | # but if it is a module then this script must do that. 16 | if [ ! -d /dev/mapper ]; then 17 | mkdir -m 0755 /dev/mapper 18 | elif [ -e /dev/mapper/dmroot ]; then 19 | echo "Qubes: FATAL error: /dev/mapper/dmroot already exists?!" >&2 20 | fi 21 | 22 | /sbin/modprobe xenblk || /sbin/modprobe xen-blkfront || echo "Qubes: Cannot load Xen Block Frontend..." 23 | 24 | die() { 25 | echo "$@" >&2 26 | exit 1 27 | } 28 | 29 | echo "Waiting for /dev/xvda* devices..." 30 | while ! [ -e /dev/xvda ]; do sleep 0.1; done 31 | # Fix up partition tables 32 | if /usr/sbin/gptfix fix /dev/xvda; then 33 | while ! [ -e /dev/xvda1 ]; do sleep 0.01; done 34 | if [ -d /dev/disk/by-partlabel ]; then 35 | ROOT_DEV=$(readlink "/dev/disk/by-partlabel/Root\\x20filesystem") 36 | ROOT_DEV=${ROOT_DEV##*/} 37 | else 38 | ROOT_DEV=$(grep -l "PARTNAME=Root filesystem" /sys/block/xvda/xvda*/uevent | 39 | grep -o "xvda[0-9]") 40 | fi 41 | if [ -z "$ROOT_DEV" ]; then 42 | # fallback to third partition 43 | ROOT_DEV=xvda3 44 | fi 45 | while ! [ -b "/dev/$ROOT_DEV" ]; do sleep 0.01; done 46 | else 47 | case $? in 48 | (1|2) # EIO, ENOMEM, or bug. Fatal. 49 | die 'Fatal error reading partition table';; 50 | (4|5|8) # Bad or no partition table 51 | ROOT_DEV=xvda;; 52 | (*) 53 | # TODO: what should be done? 54 | # - "Partition table not supported" 55 | # - "Disk truncated" 56 | die 'GPT cannot be fixed or disk truncated';; 57 | esac 58 | fi 59 | 60 | SWAP_SIZE_GiB=1 61 | SWAP_SIZE_512B=$(( SWAP_SIZE_GiB * 1024 * 1024 * 2 )) 62 | 63 | if [ `cat /sys/class/block/$ROOT_DEV/ro` = 1 ] ; then 64 | echo "Qubes: Doing COW setup for AppVM..." 65 | 66 | while ! [ -e /dev/xvdc ]; do sleep 0.1; done 67 | VOLATILE_SIZE_512B=$(cat /sys/class/block/xvdc/size) 68 | if [ $VOLATILE_SIZE_512B -lt $SWAP_SIZE_512B ]; then 69 | die "volatile.img smaller than $SWAP_SIZE_GiB GiB, cannot continue" 70 | fi 71 | /sbin/sfdisk -q /dev/xvdc >/dev/null </dev/null < /sys/devices/system/xen_memory/xen_memory0/scrub_pages 20 | fi 21 | -------------------------------------------------------------------------------- /gptfixer/layout: -------------------------------------------------------------------------------- 1 | label: gpt 2 | label-id: F4796A2A-E377-45BD-B539-D6D49E569055 3 | device: /dev/loop0 4 | unit: sectors 5 | first-lba: 24 6 | sector-size: 512 7 | table-length: 33 8 | last-lba: 41943029 9 | 10 | /dev/loop0p1 : start=32, size=409592, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=FA4D6529-56DA-47C7-AE88-E2DFECB72621, name="EFI System" 11 | /dev/loop0p2 : start=411648, size= 4096, type=21686148-6449-6E6F-744E-656564454649, uuid=1E6C9DB4-1E91-46C4-846A-2030DCB13B8C, name="BIOS boot partition" 12 | /dev/loop0p3 : start=415744, size= 4096, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=5DCECC9F-71B8-4357-8039-1BEBB7A48653, name="/home" 13 | /dev/loop0p4 : start=419840, size= 4096, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=3EA4A03F-ACCF-4EDF-99EC-8E5F2B382459, name="/usr" 14 | /dev/loop0p5 : start=423944,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 15 | /dev/loop0p6 : start=423952,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 16 | /dev/loop0p7 : start=423960,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 17 | /dev/loop0p8 : start=423968,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 18 | /dev/loop0p9 : start=423976,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 19 | /dev/loop0p10 : start=423984,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 20 | /dev/loop0p11 : start=423992,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 21 | /dev/loop0p12 : start=424000,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 22 | /dev/loop0p13 : start=424008,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 23 | /dev/loop0p14 : start=424016,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 24 | /dev/loop0p15 : start=424024,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 25 | /dev/loop0p16 : start=424032,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 26 | /dev/loop0p17 : start=424040,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 27 | /dev/loop0p18 : start=424048,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 28 | /dev/loop0p19 : start=424056,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 29 | /dev/loop0p20 : start=424064,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 30 | /dev/loop0p21 : start=424072,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 31 | /dev/loop0p22 : start=424080,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 32 | /dev/loop0p23 : start=424088,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 33 | /dev/loop0p24 : start=424096,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 34 | /dev/loop0p25 : start=424104,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 35 | /dev/loop0p26 : start=424112,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 36 | /dev/loop0p27 : start=424120,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 37 | /dev/loop0p28 : start=424128,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 38 | /dev/loop0p29 : start=424136,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 39 | /dev/loop0p30 : start=424144,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 40 | /dev/loop0p31 : start=424152,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 41 | /dev/loop0p32 : start=424160,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" 42 | /dev/loop0p33 : start=424168,size=41518848,type=0FC63DAF-8483-4772-8E79-3D69D8477DE4,uuid=693244E6-3E07-47BF-AD79-ACADE4293FE7,name="Root filesystem" 43 | -------------------------------------------------------------------------------- /gptfixer/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -- 2 | set -euo pipefail 3 | 4 | case $0 in (/*) cd "${0%/*}/";; (*/*) cd "./${0%/*}";; (*) :;; esac 5 | make -C .. gptfixer/gpt 6 | chk () { 7 | loopdev=$(sudo losetup --nooverlap --find --sector-size "$1" --show -- dummy.img) 8 | if [[ "$loopdev" != '/dev/loop0' ]]; then 9 | printf 'Loop device is not /dev/loop0 (got %q), expect test failure\n' "$loopdev" 10 | fi >&3 11 | echo Dumping broken partition table 12 | sudo sfdisk --label=gpt --dump -- "$loopdev" 13 | sudo ./gpt fix "$loopdev" 14 | echo Dumping fixed partition table 15 | sudo sfdisk --label=gpt --dump -- "$loopdev" 16 | sudo losetup -d "$loopdev" 17 | } 18 | 19 | go () ( 20 | set -x 21 | truncate -s 0 dummy.img 22 | truncate -s 20GiB dummy.img 23 | sfdisk --force dummy.img < layout | grep -v "^Syncing disks" 24 | chk 4096 25 | chk 512 26 | ) 27 | 28 | case "$#,${1-}" in 29 | ('1,update') go 3>&2 > test.sh.stdout 2> test.sh.stderr;; 30 | (0,) 31 | tmpdir=$(mktemp -d) 32 | go 3>&2 > "$tmpdir/stdout" 2> "$tmpdir/stderr" 33 | diff -u -- "$tmpdir/stdout" test.sh.stdout 34 | diff -u -- "$tmpdir/stderr" test.sh.stderr 35 | rm -rf -- "$tmpdir" 36 | ;; 37 | (*) echo "Usage: test.sh [update]" >&2; exit 1;; 38 | esac 39 | -------------------------------------------------------------------------------- /gptfixer/test.sh.stderr: -------------------------------------------------------------------------------- 1 | + truncate -s 0 dummy.img 2 | + truncate -s 20GiB dummy.img 3 | + sfdisk --force dummy.img 4 | + grep -v '^Syncing disks' 5 | + chk 4096 6 | ++ sudo losetup --nooverlap --find --sector-size 4096 --show -- dummy.img 7 | + loopdev=/dev/loop0 8 | + [[ /dev/loop0 != \/\d\e\v\/\l\o\o\p\0 ]] 9 | + echo Dumping broken partition table 10 | + sudo sfdisk --label=gpt --dump -- /dev/loop0 11 | GPT PMBR size mismatch (41943039 != 5242879) will be corrected by write. 12 | + sudo ./gpt fix /dev/loop0 13 | gpt: Found GPT with different sector size, altering 14 | + echo Dumping fixed partition table 15 | + sudo sfdisk --label=gpt --dump -- /dev/loop0 16 | + sudo losetup -d /dev/loop0 17 | + chk 512 18 | ++ sudo losetup --nooverlap --find --sector-size 512 --show -- dummy.img 19 | + loopdev=/dev/loop0 20 | + [[ /dev/loop0 != \/\d\e\v\/\l\o\o\p\0 ]] 21 | + echo Dumping broken partition table 22 | + sudo sfdisk --label=gpt --dump -- /dev/loop0 23 | GPT PMBR size mismatch (5242879 != 41943039) will be corrected by write. 24 | + sudo ./gpt fix /dev/loop0 25 | gpt: Found GPT with different sector size, altering 26 | + echo Dumping fixed partition table 27 | + sudo sfdisk --label=gpt --dump -- /dev/loop0 28 | + sudo losetup -d /dev/loop0 29 | -------------------------------------------------------------------------------- /grub/Makefile: -------------------------------------------------------------------------------- 1 | install-fedora: 2 | install -D -m 0644 grub.qubes-kernel-vm-support \ 3 | $(DESTDIR)/etc/default/grub.qubes-kernel-vm-support 4 | 5 | install-debian: 6 | install -D -m 0644 grub.qubes-kernel-vm-support \ 7 | $(DESTDIR)/etc/default/grub.d/30-qubes-kernel-vm-support.cfg 8 | -------------------------------------------------------------------------------- /grub/grub.qubes-kernel-vm-support: -------------------------------------------------------------------------------- 1 | # add kernel options only in VM, and only if initramfs is updated already 2 | # /var/lib/qubes/initramfs-updated contains "milestone" initramfs update version: 3 | # 1 - addition of xen scrub_pages enabling code 4 | if [ -r /usr/share/qubes/marker-vm ] && 5 | [ "$(cat /var/lib/qubes/initramfs-updated 2>/dev/null || echo 0)" -ge 1 ]; then 6 | GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX xen_scrub_pages=0" 7 | fi 8 | GRUB_ENABLE_BLSCFG=false 9 | # Add qubes version to boot menu on dom0 10 | if [ -f "/etc/qubes-release" ]; then 11 | GRUB_DISTRIBUTOR="$(sed 's,\(.*\) release \(.*\) (R\(.*\)),\1 (R\3),g' /etc/system-release)" 12 | fi 13 | -------------------------------------------------------------------------------- /imgconverter/Makefile: -------------------------------------------------------------------------------- 1 | PYTHON ?= python3 2 | 3 | all: 4 | $(PYTHON) setup.py build 5 | .PHONY: all 6 | 7 | clean: 8 | $(PYTHON) setup.py clean 9 | .PHONY: clean 10 | 11 | install: 12 | $(PYTHON) setup.py install -O1 $(PYTHON_PREFIX_ARG) --root $(DESTDIR) 13 | #ifeq (1,${DEBIANBUILD}) 14 | # cp *.py $(DESTDIR)/$(PYTHON_SITEARCH)/qubes/ 15 | #else 16 | # cp *.py* $(DESTDIR)/$(PYTHON_SITEARCH)/qubes/ 17 | #endif 18 | .PHONY: install 19 | -------------------------------------------------------------------------------- /imgconverter/qubesimgconverter/__init__.py: -------------------------------------------------------------------------------- 1 | '''Qubes Image Converter 2 | 3 | Toolkit for secure transfer and conversion of images between Qubes VMs.''' 4 | 5 | # The Qubes OS Project, http://www.qubes-os.org 6 | # 7 | # Copyright (C) 2013 Wojciech Porczyk 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | 23 | 24 | import os 25 | import re 26 | try: 27 | from io import BytesIO 28 | except ImportError: 29 | from cStringIO import StringIO as BytesIO 30 | import subprocess 31 | import sys 32 | import unittest 33 | import asyncio 34 | 35 | import PIL.Image 36 | import numpy 37 | 38 | # those are for "zOMG UlTRa HD! WalLLpapPer 8K!!1!" to work seamlessly; 39 | # 8192 * 5120 * 4 B = 160 MiB, so DoS by memory exhaustion is unlikely 40 | MAX_WIDTH = 8192 41 | MAX_HEIGHT = 5120 42 | 43 | # current max raster icon size in hicolor theme is 256 as of 2013/fedora-18 44 | # beyond that one probably shall use scalable icons 45 | # (SVG is currently unsupported) 46 | ICON_MAXSIZE = 2048 47 | 48 | # header consists of two decimal numbers, SPC and LF 49 | re_imghdr = re.compile(br'^\d+ \d+\n$') 50 | def imghdrlen(w, h): 51 | # width & height are inclusive max vals, and +2 for ' ' and '\n' 52 | return len(str(w)) + len(str(h)) + 2 53 | 54 | class Image(object): 55 | def __init__(self, rgba, size): 56 | '''This class is not meant to be instantiated directly. Use one of: 57 | get_from_stream(), get_from_vm(), get_xdg_icon_from_vm(), get_through_dvm()''' 58 | self._rgba = rgba 59 | self._size = size 60 | 61 | def save(self, dst): 62 | 'Save image to disk. dst may specify format, like png:aqq.gif' 63 | 64 | p = subprocess.Popen(['gm', 65 | 'convert', 66 | '-depth', '8', 67 | '-size', '{0[0]}x{0[1]}'.format(self._size), 68 | 'rgba:-', 69 | '+set', 'date:create', 70 | '+set', 'date:modify', 71 | dst], stdin=subprocess.PIPE) 72 | p.stdin.write(self._rgba) 73 | p.stdin.close() 74 | 75 | if p.wait(): 76 | raise Exception('Conversion failed') 77 | 78 | def save_pil(self, dst): 79 | '''Save image to disk using PIL.''' 80 | 81 | img = PIL.Image.frombytes('RGBA', self._size, self._rgba) 82 | img.save(dst) 83 | 84 | @property 85 | def data(self): 86 | return self._rgba 87 | 88 | @property 89 | def width(self): 90 | return self._size[0] 91 | 92 | @property 93 | def height(self): 94 | return self._size[1] 95 | 96 | def tint(self, colour): 97 | '''Return new tinted image''' 98 | 99 | tr, tg, tb = hex_to_int(colour) 100 | tM = max(tr, tg, tb) 101 | tm = min(tr, tg, tb) 102 | 103 | # (trn/tdn, tgn/tdn, tbn/tdn) is the tint color with maximum saturation 104 | if tm == tM: 105 | trn = 1 106 | tgn = 1 107 | tbn = 1 108 | tdn = 2 109 | else: 110 | trn = tr - tm 111 | tgn = tg - tm 112 | tbn = tb - tm 113 | tdn = tM - tm 114 | 115 | # use a 1D image representation since we only process a single pixel at a time 116 | pixels = self._size[0] * self._size[1] 117 | x = numpy.fromstring(self._rgba, 'B').reshape(pixels, 4) 118 | r = x[:, 0] 119 | g = x[:, 1] 120 | b = x[:, 2] 121 | a = x[:, 3] 122 | M = numpy.maximum(numpy.maximum(r, g), b).astype('u4') 123 | m = numpy.minimum(numpy.minimum(r, g), b).astype('u4') 124 | 125 | # Tn/Td is how much chroma range is reserved for the tint color 126 | # 0 -> greyscale image becomes greyscale image 127 | # 1 -> image becomes solid tint color 128 | Tn = 1 129 | Td = 4 130 | 131 | # set chroma to the original pixel chroma mapped to the Tn/Td .. 1 range 132 | # float c2 = (Tn/Td) + (1.0 - Tn/Td) * c 133 | 134 | # set lightness to the original pixel lightness mapped to the range for the new chroma value 135 | # float m2 = m * (1.0 - c2) / (1.0 - c) 136 | 137 | c = M - m 138 | 139 | c2 = (Tn * 255) + (Td - Tn) * c 140 | c2d = Td 141 | 142 | m2 = ((255 * c2d) - c2) * m 143 | # the maximum avoids division by 0 when c = 255 (m2 is 0 anyway, so m2d doesn't matter) 144 | m2d = numpy.maximum((255 - c) * c2d, 1) 145 | 146 | # precomputed values 147 | c2d_tdn = tdn * c2d 148 | m2_c2d_tdn = m2 * c2d_tdn 149 | m2d_c2d_tdn = m2d * c2d_tdn 150 | c2_m2d = c2 * m2d 151 | 152 | # float vt = m2 + tvn * c2 153 | rt = ((m2_c2d_tdn + trn * c2_m2d) // m2d_c2d_tdn).astype('B') 154 | gt = ((m2_c2d_tdn + tgn * c2_m2d) // m2d_c2d_tdn).astype('B') 155 | bt = ((m2_c2d_tdn + tbn * c2_m2d) // m2d_c2d_tdn).astype('B') 156 | 157 | xt = numpy.column_stack((rt, gt, bt, a)) 158 | return self.__class__(rgba=xt.tobytes(), size=self._size) 159 | 160 | @classmethod 161 | def load_from_file(cls, filename): 162 | '''Loads image from local file. 163 | 164 | WARNING: always load trusted images.''' 165 | 166 | p = subprocess.Popen(['gm', 'identify', '-format', '%w %h', filename], 167 | stdout=subprocess.PIPE) 168 | size = tuple(int(i) for i in p.stdout.read().strip().split()) 169 | p.stdout.close() 170 | p.wait() 171 | 172 | p = subprocess.Popen(['gm', 'convert', filename, '-depth', '8', 'rgba:-'], 173 | stdout=subprocess.PIPE) 174 | rgba = p.stdout.read() 175 | p.stdout.close() 176 | p.wait() 177 | 178 | return cls(rgba=rgba, size=size) 179 | 180 | @classmethod 181 | def load_from_file_pil(cls, filename): 182 | '''Loads image from local file using PIL.''' 183 | img = PIL.Image.open(filename) 184 | img = img.convert('RGBA') 185 | return cls(rgba=img.tobytes(), size=img.size) 186 | 187 | @classmethod 188 | def get_from_stream(cls, stream, max_width=MAX_WIDTH, max_height=MAX_HEIGHT): 189 | '''Carefully parse image data from stream. 190 | 191 | THIS METHOD IS SECURITY-SENSITIVE''' 192 | 193 | maxhdrlen = imghdrlen(max_width, max_height) 194 | 195 | untrusted_header = stream.readline(maxhdrlen) 196 | if len(untrusted_header) == 0: 197 | raise ValueError('No icon received') 198 | if not re_imghdr.match(untrusted_header): 199 | raise ValueError('Image format violation') 200 | header = untrusted_header 201 | del untrusted_header 202 | 203 | untrusted_width, untrusted_height = (int(i) for i in header.rstrip().split()) 204 | if not (0 < untrusted_width <= max_width \ 205 | and 0 < untrusted_height <= max_height): 206 | raise ValueError('Image size constraint violation:' 207 | ' width={width} height={height}' 208 | ' max_width={max_width} max_height={max_height}'.format( 209 | width=untrusted_width, height=untrusted_height, 210 | max_width=max_width, max_height=max_height)) 211 | width, height = untrusted_width, untrusted_height 212 | del untrusted_width, untrusted_height 213 | 214 | expected_data_len = width * height * 4 # RGBA 215 | untrusted_data = stream.read(expected_data_len) 216 | if len(untrusted_data) != expected_data_len: 217 | raise ValueError( \ 218 | 'Image data length violation (is {0}, should be {1})'.format( \ 219 | len(untrusted_data), expected_data_len)) 220 | data = untrusted_data 221 | del untrusted_data 222 | 223 | return cls(rgba=data, size=(width, height)) 224 | 225 | @classmethod 226 | async def get_from_stream_async( 227 | cls, reader: asyncio.StreamReader, 228 | max_width=MAX_WIDTH, max_height=MAX_HEIGHT): 229 | '''Carefully parse image data from asyncio.StreamReader. 230 | 231 | THIS METHOD IS SECURITY-SENSITIVE''' 232 | 233 | maxhdrlen = imghdrlen(max_width, max_height) 234 | 235 | # Unfortunately there is no readline(limit) method for StreamReader, 236 | # so we rely on reader's global limit to limit memory usage, and 237 | # check length in the next step (see 238 | # https://docs.python.org/3/library/asyncio-stream.html). 239 | untrusted_header = await reader.readline() 240 | 241 | if len(untrusted_header) == 0: 242 | raise ValueError('No icon received') 243 | if len(untrusted_header) > maxhdrlen: 244 | raise ValueError('Header too long ({})'.format( 245 | len(untrusted_header))) 246 | if not re_imghdr.match(untrusted_header): 247 | raise ValueError('Image format violation') 248 | header = untrusted_header 249 | del untrusted_header 250 | 251 | untrusted_width, untrusted_height = (int(i) for i in header.rstrip().split()) 252 | if not (0 < untrusted_width <= max_width \ 253 | and 0 < untrusted_height <= max_height): 254 | raise ValueError('Image size constraint violation:' 255 | ' width={width} height={height}' 256 | ' max_width={max_width} max_height={max_height}'.format( 257 | width=untrusted_width, height=untrusted_height, 258 | max_width=max_width, max_height=max_height)) 259 | width, height = untrusted_width, untrusted_height 260 | del untrusted_width, untrusted_height 261 | 262 | expected_data_len = width * height * 4 # RGBA 263 | untrusted_data = await reader.readexactly(expected_data_len) 264 | if len(untrusted_data) != expected_data_len: 265 | raise ValueError( \ 266 | 'Image data length violation (is {0}, should be {1})'.format( \ 267 | len(untrusted_data), expected_data_len)) 268 | data = untrusted_data 269 | del untrusted_data 270 | 271 | return cls(rgba=data, size=(width, height)) 272 | 273 | @classmethod 274 | def get_from_vm(cls, vm, src, **kwargs): 275 | 'Get image from VM by QUBESRPC (qubes.GetImageRGBA).' 276 | 277 | p = vm.run_service('qubes.GetImageRGBA') 278 | p.stdin.write('{0}\n'.format(src).encode()) 279 | p.stdin.close() 280 | 281 | try: 282 | img = cls.get_from_stream(p.stdout, **kwargs) 283 | finally: 284 | p.stdout.close() 285 | if p.wait(): 286 | raise Exception('Something went wrong with receiver') 287 | 288 | return img 289 | 290 | @classmethod 291 | def get_xdg_icon_from_vm(cls, vm, icon, **kwargs): 292 | 'Get image from VM. If path is not absolute, get it from hicolor theme.' 293 | 294 | if not os.path.isabs(icon): 295 | icon = 'xdgicon:' + icon 296 | return cls.get_from_vm(vm, icon, 297 | max_width=ICON_MAXSIZE, max_height=ICON_MAXSIZE, **kwargs) 298 | 299 | @classmethod 300 | def get_through_dvm(cls, filename, **kwargs): 301 | '''Master end of image filter: writes untrusted image to stdout and 302 | expects header+RGBA on stdin. This method is invoked from qvm-imgconverter-client.''' 303 | 304 | if ':' in filename: 305 | filetype, filename = filename.split(':', 1) 306 | sys.stdout.buffer.write('{0}:-\n'.format(filetype).encode()) 307 | else: 308 | sys.stdout.buffer.write(b'-\n') 309 | 310 | try: 311 | with open(filename, 'rb') as f: 312 | sys.stdout.buffer.write(f.read()) 313 | except Exception as e: 314 | raise Exception('Something went wrong: {0!s}'.format(e)) 315 | finally: 316 | sys.stdout.buffer.close() 317 | # sys.stdout.close() is not enough and documentation is silent about this 318 | os.close(1) 319 | 320 | return cls.get_from_stream(sys.stdin.buffer, **kwargs) 321 | 322 | def __eq__(self, other): 323 | return self._size == other._size and self._rgba == other._rgba 324 | 325 | def __ne__(self, other): 326 | return not self.__eq__(other) 327 | 328 | def hex_to_int(colour, channels=3, depth=1): 329 | '''Convert hex colour definition to tuple of ints.''' 330 | 331 | length = channels * depth * 2 332 | step = depth * 2 333 | 334 | # get rid of '#' or '0x' in front of hex values 335 | colour = colour[-length:] 336 | 337 | return tuple(int(colour[i:i+step], 0x10) for i in range(0, length, step)) 338 | 339 | def tint(src, dst, colour): 340 | '''Tint image to reflect vm label. 341 | 342 | src and dst may NOT specify GraphicsMagick format''' 343 | 344 | Image.load_from_file_pil(src).tint(colour).save_pil(dst) 345 | 346 | 347 | # vim: ft=python sw=4 ts=4 et 348 | -------------------------------------------------------------------------------- /imgconverter/qubesimgconverter/imggen.py: -------------------------------------------------------------------------------- 1 | '''Qubes Image Generation 2 | 3 | Toolkit for generating icons and images for Qubes OS.''' 4 | 5 | # The Qubes OS Project, http://www.qubes-os.org 6 | # 7 | # Copyright (C) 2013-2015 Wojtek Porczyk 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | 23 | from __future__ import absolute_import 24 | 25 | __all__ = ['make_padlock'] 26 | 27 | import math 28 | import cairo 29 | 30 | import qubesimgconverter 31 | 32 | def polar(r, a): 33 | return r * math.cos(a), r * math.sin(a) 34 | 35 | def make_padlock(dst, colour, size=qubesimgconverter.ICON_MAXSIZE, disp=False): 36 | cs = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size) 37 | 38 | cr = cairo.Context(cs) 39 | cr.set_source_rgb(*[c / 256.0 40 | for c in qubesimgconverter.hex_to_int(colour)]) 41 | cr.set_line_width(.125 * size) 42 | 43 | cr.rectangle(.125 * size, .5 * size, .75 * size, .4375 * size) 44 | cr.fill() 45 | 46 | cr.move_to(.25 * size, .5 * size) 47 | cr.line_to(.25 * size, .375 * size) 48 | cr.arc(.5 * size, .375 * size, .25 * size, math.pi, 2 * math.pi) 49 | cr.move_to(.75 * size, .375 * size) # this is unneccessary, but helps readability 50 | cr.line_to(.75 * size, .5 * size) 51 | cr.stroke() 52 | 53 | if disp: 54 | # Careful with those. I have run into severe 55 | # floating point errors when adjusting. 56 | arrows = 2 57 | gap = 45 * math.pi / 180 58 | offset = 0 59 | radius = .1875 * size 60 | width = 0.05 * size 61 | cx = .5 * size 62 | # cy = .6875 * size 63 | cy = .625 * size 64 | 65 | arrow = 2 * math.pi / arrows 66 | 67 | for i in range(arrows): 68 | cr.move_to(cx, cy) 69 | cr.rel_move_to(*polar( radius - width, offset + i * arrow)) 70 | cr.arc(cx, cy, radius - width, offset + i * arrow, offset + (i + 1) * arrow - gap) 71 | cr.rel_line_to(*polar( width, offset + (i + 1) * arrow - gap + math.pi)) 72 | cr.rel_line_to(*polar( width * math.sqrt(8), offset + (i + 1) * arrow - gap + math.pi / 4)) 73 | cr.rel_line_to(*polar( width * math.sqrt(8), offset + (i + 1) * arrow - gap - math.pi / 4)) 74 | cr.rel_line_to(*polar( width, offset + (i + 1) * arrow - gap + math.pi)) 75 | cr.arc_negative(cx, cy, radius + width, offset + (i + 1) * arrow - gap, offset + i * arrow) 76 | cr.close_path() 77 | 78 | cr.set_source_rgb(0xcc / 256.0, 0, 0) # tango's red 79 | cr.set_line_width(.0500 * size) 80 | cr.set_line_join(cairo.LINE_JOIN_ROUND) 81 | cr.stroke_preserve() 82 | 83 | cr.set_source_rgb(1.0, 1.0, 1.0) 84 | cr.fill() 85 | 86 | cs.write_to_png(dst) 87 | 88 | # vim: ft=python sw=4 ts=4 et 89 | -------------------------------------------------------------------------------- /imgconverter/qubesimgconverter/test.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import asyncio 4 | 5 | try: 6 | from io import BytesIO 7 | except ImportError: 8 | from cStringIO import StringIO as BytesIO 9 | import unittest 10 | 11 | 12 | import qubesimgconverter 13 | 14 | class TestCaseImage(unittest.IsolatedAsyncioTestCase): 15 | def setUp(self): 16 | self.rgba = \ 17 | b'\x00\x00\x00\xff' b'\xff\x00\x00\xff' \ 18 | b'\x00\xff\x00\xff' b'\x00\x00\x00\xff' 19 | self.size = (2, 2) 20 | 21 | self.image = qubesimgconverter.Image(rgba=self.rgba, size=self.size) 22 | 23 | def test_00_init(self): 24 | self.assertEqual(self.image._rgba, self.rgba) 25 | self.assertEqual(self.image._size, self.size) 26 | 27 | def test_01_tint(self): 28 | image = self.image.tint('#0000ff') 29 | 30 | self.assertEqual(image._rgba, 31 | b'\x00\x00\x3f\xff' b'\x00\x00\xff\xff' 32 | b'\x00\x00\xff\xff' b'\x00\x00\x3f\xff') 33 | 34 | def test_10_get_from_stream(self): 35 | io = BytesIO('{0[0]} {0[1]}\n'.format(self.size).encode() + self.rgba) 36 | 37 | image = qubesimgconverter.Image.get_from_stream(io) 38 | 39 | self.assertEqual(image._rgba, self.rgba) 40 | self.assertEqual(image._size, self.size) 41 | 42 | def test_11_get_from_stream_malformed(self): 43 | io = BytesIO('{0[0]} {0[1]}\n'.format(self.size).encode() + 44 | self.rgba[:-1]) # one byte too short 45 | 46 | with self.assertRaisesRegex(ValueError, 'data length violation'): 47 | image = qubesimgconverter.Image.get_from_stream(io) 48 | 49 | def test_12_get_from_stream_too_big(self): 50 | io = BytesIO('{0[0]} {0[1]}\n'.format(self.size).encode() + self.rgba) # 2x2 51 | 52 | with self.assertRaisesRegex(ValueError, 'size constraint violation'): 53 | image = qubesimgconverter.Image.get_from_stream(io, max_width=1) 54 | 55 | io.seek(0) 56 | with self.assertRaisesRegex(ValueError, 'size constraint violation'): 57 | image = qubesimgconverter.Image.get_from_stream(io, max_height=1) 58 | 59 | async def test_20_get_from_stream_async(self): 60 | reader = asyncio.StreamReader() 61 | reader.feed_data('{0[0]} {0[1]}\n'.format(self.size).encode() + self.rgba) 62 | 63 | image = await qubesimgconverter.Image.get_from_stream_async(reader) 64 | 65 | self.assertEqual(image._rgba, self.rgba) 66 | self.assertEqual(image._size, self.size) 67 | 68 | async def test_21_get_from_stream_malformed_async(self): 69 | reader = asyncio.StreamReader() 70 | reader.feed_data('{0[0]} {0[1]}\n'.format(self.size).encode() + 71 | self.rgba[:-1]) # one byte too short 72 | reader.feed_eof() 73 | 74 | with self.assertRaises(asyncio.IncompleteReadError): 75 | image = await qubesimgconverter.Image.get_from_stream_async(reader) 76 | 77 | async def test_22_get_from_stream_too_big(self): 78 | data = '{0[0]} {0[1]}\n'.format(self.size).encode() + self.rgba # 2x2 79 | 80 | reader = asyncio.StreamReader() 81 | reader.feed_data(data) 82 | with self.assertRaisesRegex(ValueError, 'size constraint violation'): 83 | image = await qubesimgconverter.Image.get_from_stream_async(reader, max_width=1) 84 | 85 | reader = asyncio.StreamReader() 86 | reader.feed_data(data) 87 | with self.assertRaisesRegex(ValueError, 'size constraint violation'): 88 | image = await qubesimgconverter.Image.get_from_stream_async(reader, max_height=1) 89 | 90 | async def test_23_get_from_stream_header_too_long(self): 91 | data = '{0[0]} {0[1]}\n'.format(self.size).encode() + self.rgba # 2x2 92 | reader = asyncio.StreamReader() 93 | reader.feed_data(b'x' * 20 + b'\n') 94 | with self.assertRaisesRegex(ValueError, 'Header too long'): 95 | image = await qubesimgconverter.Image.get_from_stream_async(reader) 96 | 97 | 98 | class TestCaseFunctionsAndConstants(unittest.TestCase): 99 | def test_00_imghdrlen(self): 100 | self.assertEqual(qubesimgconverter.imghdrlen(8, 15), len('8 15\n')) 101 | self.assertEqual(qubesimgconverter.imghdrlen(100, 100), len('100 100\n')) 102 | 103 | def test_01_re_imghdr(self): 104 | self.assertTrue(qubesimgconverter.re_imghdr.match(b'8 15\n')) 105 | self.assertIsNone(qubesimgconverter.re_imghdr.match(b'8 15')) 106 | self.assertIsNone(qubesimgconverter.re_imghdr.match(b'815\n')) 107 | self.assertIsNone(qubesimgconverter.re_imghdr.match(b'x yx\n')) 108 | 109 | def test_10_hex_to_float_result_00(self): 110 | self.assertEqual(qubesimgconverter.hex_to_int('#000000'), (0, 0, 0)) 111 | 112 | def test_11_hex_to_float_result_ff(self): 113 | self.assertEqual(qubesimgconverter.hex_to_int('0xffffff'), 114 | (0xff, 0xff, 0xff)) 115 | 116 | def test_12_hex_to_float_depth_3_not_implemented(self): 117 | with self.assertRaises(ValueError): 118 | qubesimgconverter.hex_to_int('123456', depth=3) 119 | 120 | if __name__ == '__main__': 121 | unittest.main() 122 | -------------------------------------------------------------------------------- /imgconverter/qubesimgconverter/test_integ.py: -------------------------------------------------------------------------------- 1 | # vim: fileencoding=utf-8 2 | 3 | # 4 | # The Qubes OS Project, https://www.qubes-os.org/ 5 | # 6 | # Copyright (C) 2017 7 | # Marek Marczykowski-Górecki 8 | # 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License along 20 | # with this program; if not, write to the Free Software Foundation, Inc., 21 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | # 23 | 24 | import itertools 25 | import qubes.tests.extra 26 | 27 | 28 | # noinspection PyPep8Naming 29 | class TC_00_ImgConverter(qubes.tests.extra.ExtraTestCase): 30 | def setUp(self): 31 | super(TC_00_ImgConverter, self).setUp() 32 | # noinspection PyAttributeOutsideInit 33 | self.vm = self.create_vms(["vm"])[0] 34 | self.vm.start() 35 | 36 | self.image_size = 16 37 | # RGB data for the image 38 | self.image_data = [ 39 | (0xff // self.image_size * x, 0x80, 0xff // self.image_size * y, 40 | 0xff) 41 | for x in range(self.image_size) 42 | for y in range(self.image_size)] 43 | 44 | def create_img(self, filename): 45 | '''Create image file with given sample content 46 | 47 | :param filename: output filename 48 | ''' 49 | p = self.vm.run( 50 | 'gm convert -size {}x{} -depth 8 rgba:- "{}" 2>&1'.format( 51 | self.image_size, self.image_size, filename), 52 | passio_popen=True) 53 | bytes_data = bytes(bytearray(itertools.chain(*self.image_data))) 54 | (stdout, _) = p.communicate(bytes_data) 55 | if p.returncode != 0: 56 | self.skipTest('failed to create test image: {}'.format(stdout)) 57 | 58 | def assertCorrectlyTransformed(self, orig_filename, trusted_filename): 59 | self.assertEqual( 60 | self.vm.run('test -r "{}"'.format(trusted_filename), wait=True), 0) 61 | self.assertEqual( 62 | self.vm.run('test -r "{}"'.format(orig_filename), wait=True), 0) 63 | # retrieve original image too, to compensate for compression 64 | p = self.vm.run('gm convert "{}" rgb:-'.format(orig_filename), 65 | passio_popen=True) 66 | orig_image_data, _ = p.communicate() 67 | p = self.vm.run('gm convert "{}" rgb:-'.format(trusted_filename), 68 | passio_popen=True) 69 | trusted_image_data, _ = p.communicate() 70 | self.assertEqual(orig_image_data, trusted_image_data) 71 | 72 | def test_000_png(self): 73 | self.create_img('test.png') 74 | p = self.vm.run('qvm-convert-img test.png trusted.png 2>&1', 75 | passio_popen=True) 76 | (stdout, _) = p.communicate() 77 | if p.returncode == 127: 78 | self.skipTest('qubes-img-converter not installed') 79 | self.assertEqual(p.returncode, 0, 'qvm-convert-img failed: {}'.format( 80 | stdout)) 81 | self.assertCorrectlyTransformed('test.png', 'trusted.png') 82 | 83 | def test_010_filename_with_spaces(self): 84 | self.create_img('test with spaces.png') 85 | p = self.vm.run('qvm-convert-img "test with spaces.png" ' 86 | '"trusted with spaces.png" 2>&1', 87 | passio_popen=True) 88 | (stdout, _) = p.communicate() 89 | if p.returncode == 127: 90 | self.skipTest('qubes-img-converter not installed') 91 | self.assertEqual(p.returncode, 0, 'qvm-convert-img failed: {}'.format( 92 | stdout)) 93 | self.assertCorrectlyTransformed( 94 | 'test with spaces.png', 'trusted with spaces.png') 95 | 96 | 97 | def list_tests(): 98 | tests = [TC_00_ImgConverter] 99 | return tests 100 | -------------------------------------------------------------------------------- /imgconverter/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | name='qubesimgconverter', 5 | version=open('../version').read().strip(), 6 | author='Invisible Things Lab', 7 | author_email='woju@invisiblethingslab.com', 8 | description='Toolkit for secure transfer and conversion of images between Qubes VMs.', 9 | license='GPL2+', 10 | url='https://www.qubes-os.org/', 11 | packages=['qubesimgconverter'], 12 | entry_points={ 13 | 'qubes.tests.extra.for_template': 14 | 'qubesimgconverter = qubesimgconverter.test_integ:list_tests', 15 | } 16 | 17 | ) 18 | 19 | # vim: ts=4 sts=4 sw=4 et 20 | -------------------------------------------------------------------------------- /initramfs-tools/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | install -D local-top/qubes_cow_setup.sh \ 3 | $(DESTDIR)/usr/share/initramfs-tools/scripts/local-top/qubes_cow_setup 4 | install -D local-top/scrub_pages.sh \ 5 | $(DESTDIR)/usr/share/initramfs-tools/scripts/local-top/scrub_pages 6 | install -D qubes_vm \ 7 | $(DESTDIR)/usr/share/initramfs-tools/hooks/qubes_vm 8 | install -m 0644 -D qubes.conf \ 9 | $(DESTDIR)/usr/share/initramfs-tools/conf.d/qubes.conf 10 | 11 | -------------------------------------------------------------------------------- /initramfs-tools/local-top/qubes_cow_setup.sh: -------------------------------------------------------------------------------- 1 | ../../dracut/full-dmroot/qubes_cow_setup.sh -------------------------------------------------------------------------------- /initramfs-tools/local-top/scrub_pages.sh: -------------------------------------------------------------------------------- 1 | ../../dracut/xen-balloon-scrub-pages/scrub_pages.sh -------------------------------------------------------------------------------- /initramfs-tools/qubes.conf: -------------------------------------------------------------------------------- 1 | # reduce initramfs size 2 | MODULES=dep 3 | # there is no suspend-to-disk for VMs 4 | RESUME=none 5 | -------------------------------------------------------------------------------- /initramfs-tools/qubes_vm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if grep -q '^[0-]*$' /sys/hypervisor/uuid; then 4 | echo "Not intended for dom0" 5 | exit 0 6 | fi 7 | 8 | PREREQS="dmsetup" 9 | case "$1" in 10 | prereqs) 11 | echo "$PREREQS" 12 | exit 0 13 | ;; 14 | esac 15 | 16 | . /usr/share/initramfs-tools/hook-functions 17 | 18 | copy_exec /sbin/sfdisk 19 | copy_exec /sbin/mkswap 20 | copy_exec /usr/sbin/gptfix 21 | force_load xen-blkfront 22 | force_load dm-snapshot 23 | -------------------------------------------------------------------------------- /kernel-modules/Makefile: -------------------------------------------------------------------------------- 1 | MAKEFLAGS ::= -rR 2 | LDLIBS += $(shell pkg-config --libs ext2fs com_err) 3 | CFLAGS += $(shell pkg-config --cflags ext2fs com_err) \ 4 | -fstack-protector-all -O2 -fwrapv \ 5 | -fno-delete-null-pointer-checks -fno-strict-aliasing -Wall -Wextra \ 6 | -Werror=format-security -pedantic-errors -Wformat=2 -Wmaybe-uninitialized \ 7 | -Wshadow -g3 8 | SBINDIR ?= /usr/sbin 9 | 10 | all: genfs 11 | 12 | genfs: genfs.c 13 | gcc $(CFLAGS) genfs.c $(LDLIBS) -o $@ 14 | 15 | install: install-scripts install-genfs 16 | 17 | install-scripts: 18 | install -d $(DESTDIR)$(SBINDIR) 19 | install qubes-prepare-vm-kernel $(DESTDIR)$(SBINDIR) 20 | 21 | install-genfs: 22 | install -d $(DESTDIR)/usr/lib/qubes 23 | install genfs $(DESTDIR)/usr/lib/qubes/vm-modules-genfs 24 | 25 | .PHONY: clean 26 | clean: 27 | rm -f genfs 28 | -------------------------------------------------------------------------------- /kernel-modules/qubes-prepare-vm-kernel: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # The Qubes OS Project, http://www.qubes-os.org 4 | # 5 | # Copyright (C) 2015 Marek Marczykowski-Górecki 6 | # 7 | # 8 | # This program is free software; you can redistribute it and/or 9 | # modify it under the terms of the GNU General Public License 10 | # as published by the Free Software Foundation; either version 2 11 | # of the License, or (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | # 22 | # 23 | 24 | set -e 25 | 26 | basedir=/var/lib/qubes/vm-kernels 27 | 28 | function build_modules_img() { 29 | kver=$1 30 | initramfs=$2 31 | kernel=$3 32 | output_file=$4 33 | 34 | local tmpdir 35 | tmpdir=$(mktemp -d -p /var/tmp) 36 | 37 | mkdir "$tmpdir/modules" 38 | cp -a -t "$tmpdir/modules" /lib/modules/$kver 39 | cp "$kernel" "$tmpdir/modules/vmlinuz" 40 | cp "$initramfs" "$tmpdir/modules/initramfs" 41 | 42 | rm -f "$tmpdir/modules/$kver/build" 43 | if [ "$include_devel" ]; then 44 | cp -a "/usr/src/kernels/$kver" "$tmpdir/modules/$kver/build" 45 | fi 46 | 47 | flags="-Enum_backup_sb=0,root_owner=0:0,no_copy_xattrs" 48 | flags="$flags,hash_seed=dcee2318-92bd-47a5-a15d-e79d1412cdce" 49 | PATH=/usr/sbin:/sbin:$PATH mkfs.ext3 -q -F "$flags" \ 50 | -d "$tmpdir/modules" "$tmpdir/modules.img" 768M 51 | /usr/lib/qubes/vm-modules-genfs "$tmpdir/modules.img" "$kver" immutable=yes 52 | e2fsck -pDfE optimize_extents -- "$tmpdir/modules.img" >/dev/null 53 | resize2fs -fM -- "$tmpdir/modules.img" >/dev/null 54 | e2fsck -pDfE optimize_extents -- "$tmpdir/modules.img" >/dev/null 55 | /usr/lib/qubes/vm-modules-genfs "$tmpdir/modules.img" 56 | 57 | mv "$tmpdir/modules.img" $output_file 58 | rm -rf "$tmpdir" 59 | } 60 | 61 | function build_initramfs() { 62 | kver=$1 63 | output_file=$2 64 | 65 | dracut --nomdadmconf --nolvmconf --force --no-hostonly \ 66 | --no-early-microcode \ 67 | --modules "kernel-modules qubes-vm-simple busybox" \ 68 | --omit "nss-softokn extra-modules qubes-pciback qubes-udev" \ 69 | --conf /dev/null --confdir /var/empty \ 70 | -d "xenblk xen-blkfront cdrom ext4 jbd2 crc16 dm_snapshot" \ 71 | $output_file $kver 72 | chmod 644 "$output_file" 73 | } 74 | 75 | function usage() { 76 | echo "Usage: qubes-prepare-vm-kernel [--modules-only] [--include-devel] []" >&2 77 | echo " --modules-only - only build modules.img, vmlinuz is expected to be present already" >&2 78 | echo " --include-devel - include kernel headers in modules.img, requires kernel-devel pkg installed" >&2 79 | exit 2 80 | } 81 | 82 | modules_only= 83 | include_devel= 84 | 85 | while [ -n "$1" ]; do 86 | case "$1" in 87 | --modules-only) 88 | modules_only=1 89 | shift 90 | ;; 91 | --include-devel) 92 | include_devel=1 93 | shift 94 | ;; 95 | --*) usage;; 96 | *) break;; 97 | esac 98 | done 99 | 100 | if [ "$#" -ne 1 ] && [ "$#" -ne 2 ]; then 101 | usage 102 | fi 103 | 104 | kernel_version="$1" 105 | if [ -n "$2" ]; then 106 | output_dir="$basedir/$2" 107 | else 108 | output_dir="$basedir/$kernel_version" 109 | fi 110 | 111 | 112 | if [ ! -d "/lib/modules/$kernel_version" ]; then 113 | echo "ERROR: Modules version $kernel_version not installed" >&2 114 | exit 1 115 | fi 116 | 117 | if [ ! "$modules_only" ] && [ ! -r "/boot/vmlinuz-$kernel_version" ]; then 118 | echo "ERROR: Kernel version $kernel_version not installed" >&2 119 | exit 1 120 | fi 121 | 122 | if [ "$modules_only" ] && [ ! -r "$output_dir/vmlinuz" ]; then 123 | echo "ERROR: --modules-only requires '$output_dir/vmlinuz' to already exist" >&2 124 | exit 1 125 | fi 126 | 127 | if [ "$include_devel" ] && [ ! -d "/usr/src/kernels/$kernel_version" ]; then 128 | echo "ERROR: --include-devel requires kernel-devel package (containing '/usr/src/kernels/$kernel_version')" >&2 129 | exit 1 130 | fi 131 | 132 | echo "--> Building files for $kernel_version in $output_dir" 133 | 134 | if ! [ "$modules_only" ]; then 135 | mkdir -p "$output_dir" 136 | cp "/boot/vmlinuz-$kernel_version" "$output_dir/vmlinuz" 137 | echo "---> Generating initramfs" 138 | build_initramfs "$kernel_version" "$output_dir/initramfs" 139 | fi 140 | echo "---> Generating modules.img" 141 | build_modules_img "$kernel_version" "$output_dir/initramfs" \ 142 | "$output_dir/vmlinuz" \ 143 | "$output_dir/modules.img" 144 | 145 | echo "--> Done." 146 | -------------------------------------------------------------------------------- /not-script/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS += -g -O2 -Wall -Wextra -Werror -fPIE -D_GNU_SOURCE -Wmaybe-uninitialized 3 | .PHONY: clean install all 4 | all: not-script 5 | 6 | %: %.c Makefile 7 | s=$$(pkg-config --cflags --libs xenstore) && $(CC) $(CFLAGS) -MD -MP -MF $@.dep -o $@ $< $$s 8 | 9 | clean: 10 | rm -f ./*.o ./*~ ./*.a ./*.so.* ./*.dep not-script 11 | 12 | install: 13 | install -d $(DESTDIR)/etc/xen/scripts 14 | install not-script $(DESTDIR)/etc/xen/scripts/qubes-block 15 | 16 | -include ./*.o.dep 17 | -------------------------------------------------------------------------------- /qmemman/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS+=-Wall -Wextra -Werror -g -O3 3 | all: meminfo-writer 4 | BINDIR?=/usr/bin 5 | 6 | _XENSTORE_H=$(shell ls /usr/include/xenstore.h) 7 | ifneq "$(_XENSTORE_H)" "" 8 | CFLAGS+= -DUSE_XENSTORE_H 9 | endif 10 | 11 | meminfo-writer: meminfo-writer.o 12 | $(CC) $(LDFLAGS) -g -o meminfo-writer meminfo-writer.o -lxenstore 13 | install: 14 | install -D meminfo-writer $(DESTDIR)/$(BINDIR)/meminfo-writer 15 | ifeq (1,${DEBIANBUILD}) 16 | install -d $(DESTDIR)/lib/systemd/system/ 17 | install -m 0644 qubes-meminfo-writer.service $(DESTDIR)/lib/systemd/system/ 18 | else 19 | install -d $(DESTDIR)/usr/lib/systemd/system/ 20 | install -m 0644 qubes-meminfo-writer*service $(DESTDIR)/usr/lib/systemd/system/ 21 | endif 22 | clean: 23 | rm -f meminfo-writer xenstore-watch *.o 24 | -------------------------------------------------------------------------------- /qmemman/meminfo-writer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #ifdef USE_XENSTORE_H /* Xen >= 4.2 */ 6 | #include 7 | #else 8 | #include 9 | #endif 10 | #include 11 | #include 12 | #include 13 | 14 | long prev_used_mem; 15 | int used_mem_change_threshold; 16 | int delay; 17 | int usr1_received; 18 | 19 | const char *parse(const char *meminfo_buf, const char* dom_current_buf) 20 | { 21 | const char *ptr = meminfo_buf; 22 | static char outbuf[4096]; 23 | long long val; 24 | int len; 25 | int ret; 26 | long long MemTotal = 0, MemFree = 0, Buffers = 0, Cached = 0, SwapTotal = 27 | 0, SwapFree = 0; 28 | unsigned long long key; 29 | long long used_mem, used_mem_diff; 30 | int nitems = 0; 31 | 32 | while (nitems != (1<<6)-1 || !*ptr) { 33 | ret = sscanf(ptr, "%*s %lld kB\n%n", &val, &len); 34 | if (ret < 1 || len < (int)sizeof (unsigned long long)) { 35 | ptr += len; 36 | continue; 37 | } 38 | key = *(unsigned long long *) ptr; 39 | if (key == *(unsigned long long *) "MemTotal:") { 40 | MemTotal = val; 41 | nitems |= 1; 42 | } else if (key == *(unsigned long long *) "MemFree:") { 43 | MemFree = val; 44 | nitems |= 2; 45 | } else if (key == *(unsigned long long *) "Buffers:") { 46 | Buffers = val; 47 | nitems |= 4; 48 | } else if (key == *(unsigned long long *) "Cached: ") { 49 | Cached = val; 50 | nitems |= 8; 51 | } else if (key == *(unsigned long long *) "SwapTotal:") { 52 | SwapTotal = val; 53 | nitems |= 16; 54 | } else if (key == *(unsigned long long *) "SwapFree:") { 55 | SwapFree = val; 56 | nitems |= 32; 57 | } 58 | 59 | ptr += len; 60 | } 61 | 62 | if(dom_current_buf) { 63 | long long DomTotal = strtoll(dom_current_buf, 0, 10); 64 | if(DomTotal) 65 | MemTotal = DomTotal; 66 | } 67 | 68 | used_mem = 69 | MemTotal - Buffers - Cached - MemFree + SwapTotal - SwapFree; 70 | if (used_mem < 0) 71 | return NULL; 72 | 73 | used_mem_diff = used_mem - prev_used_mem; 74 | if (used_mem_diff < 0) 75 | used_mem_diff = -used_mem_diff; 76 | if (used_mem_diff > used_mem_change_threshold 77 | || prev_used_mem == 0 78 | || (used_mem > prev_used_mem && used_mem / 10 > (MemTotal+12) / 13 79 | && used_mem_diff > used_mem_change_threshold/2)) { 80 | prev_used_mem = used_mem; 81 | sprintf(outbuf, "%lld", used_mem); 82 | return outbuf; 83 | } 84 | return NULL; 85 | } 86 | 87 | void usage(void) 88 | { 89 | fprintf(stderr, 90 | "usage: meminfo_writer threshold_in_kb delay_in_us [pidfile]\n"); 91 | fprintf(stderr, " When pidfile set, meminfo-writer will:\n"); 92 | fprintf(stderr, " - fork into background\n"); 93 | fprintf(stderr, " - wait for SIGUSR1 (in background) before starting main work\n"); 94 | exit(1); 95 | } 96 | 97 | void send_to_qmemman(struct xs_handle *xs, const char *data) 98 | { 99 | if (!xs_write(xs, XBT_NULL, "memory/meminfo", data, strlen(data))) { 100 | syslog(LOG_DAEMON | LOG_ERR, "error writing xenstore ?"); 101 | exit(1); 102 | } 103 | } 104 | 105 | void usr1_handler(int sig __attribute__((__unused__))) { 106 | usr1_received = 1; 107 | } 108 | 109 | static inline void pread0_string(int fd, char* buf, size_t buf_size) 110 | { 111 | int n = pread(fd, buf, buf_size - 1, 0); 112 | if (n < 0) { 113 | perror("pread"); 114 | exit(1); 115 | } 116 | buf[n] = 0; 117 | } 118 | 119 | static void update(struct xs_handle *xs, int meminfo_fd, int dom_current_fd) 120 | { 121 | char dom_current_buf[32]; 122 | char dom_current_buf2[32]; 123 | char meminfo_buf[4096]; 124 | const char *meminfo_data; 125 | 126 | pread0_string(dom_current_fd, dom_current_buf, sizeof(dom_current_buf)); 127 | 128 | /* check until the dom current reading is stable to avoid races */ 129 | for(;;) { 130 | pread0_string(meminfo_fd, meminfo_buf, sizeof(meminfo_buf)); 131 | pread0_string(dom_current_fd, dom_current_buf2, sizeof(dom_current_buf2)); 132 | 133 | if(!strcmp(dom_current_buf, dom_current_buf2)) 134 | break; 135 | 136 | pread0_string(meminfo_fd, meminfo_buf, sizeof(meminfo_buf)); 137 | pread0_string(dom_current_fd, dom_current_buf, sizeof(dom_current_buf)); 138 | 139 | if(!strcmp(dom_current_buf, dom_current_buf2)) 140 | break; 141 | } 142 | 143 | meminfo_data = parse(meminfo_buf, dom_current_buf); 144 | if (meminfo_data) 145 | send_to_qmemman(xs, meminfo_data); 146 | } 147 | 148 | int main(int argc, char **argv) 149 | { 150 | int meminfo_fd, dom_current_fd; 151 | struct xs_handle *xs; 152 | int n; 153 | 154 | if (argc != 3 && argc != 4) 155 | usage(); 156 | used_mem_change_threshold = atoi(argv[1]); 157 | delay = atoi(argv[2]); 158 | if (used_mem_change_threshold <= 0 || delay <= 0) 159 | usage(); 160 | 161 | if (argc == 4) { 162 | pid_t pid; 163 | sigset_t mask, oldmask; 164 | int fd; 165 | char buf[32]; 166 | 167 | switch (pid = fork()) { 168 | case -1: 169 | perror("fork"); 170 | exit(1); 171 | case 0: 172 | sigemptyset (&mask); 173 | sigaddset (&mask, SIGUSR1); 174 | /* Wait for a signal to arrive. */ 175 | sigprocmask (SIG_BLOCK, &mask, &oldmask); 176 | usr1_received = 0; 177 | signal(SIGUSR1, usr1_handler); 178 | while (!usr1_received) 179 | sigsuspend (&oldmask); 180 | sigprocmask (SIG_UNBLOCK, &mask, NULL); 181 | break; 182 | default: 183 | fd = open(argv[3], O_CREAT | O_TRUNC | O_WRONLY, 0666); 184 | if (fd < 0) { 185 | perror("open pidfile"); 186 | kill(pid,9); 187 | exit(1); 188 | } 189 | n = sprintf(buf, "%d\n", pid); 190 | if (write(fd, buf, n) != n) { 191 | perror("write pid"); 192 | kill(pid,9); 193 | exit(1); 194 | } 195 | close(fd); 196 | exit(0); 197 | } 198 | } 199 | 200 | meminfo_fd = open("/proc/meminfo", O_RDONLY); 201 | if (meminfo_fd < 0) { 202 | perror("open /proc/meminfo"); 203 | exit(1); 204 | } 205 | dom_current_fd = open("/sys/devices/system/xen_memory/xen_memory0/info/current_kb", O_RDONLY); 206 | if (dom_current_fd < 0) { 207 | perror("open /sys/devices/system/xen_memory/xen_memory0/info/current_kb"); 208 | exit(1); 209 | } 210 | xs = xs_domain_open(); 211 | if (!xs) { 212 | perror("xs_domain_open"); 213 | exit(1); 214 | } 215 | if (argc == 3) { 216 | /* if not waiting for signal, fork after first info written to xenstore */ 217 | update(xs, meminfo_fd, dom_current_fd); 218 | 219 | n = fork(); 220 | if (n < 0) { 221 | perror("fork"); 222 | exit(1); 223 | } 224 | if (n > 0) 225 | exit(0); 226 | usleep(delay); 227 | } 228 | 229 | for (;;) { 230 | update(xs, meminfo_fd, dom_current_fd); 231 | usleep(delay); 232 | } 233 | } 234 | 235 | -------------------------------------------------------------------------------- /qmemman/qubes-meminfo-writer-dom0.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Qubes memory information reporter 3 | Before=systemd-user-sessions.service 4 | After=qubes-core.service qubes-qmemman.service 5 | ConditionPathExists=/run/qubes/qmemman.sock 6 | 7 | [Service] 8 | Type=simple 9 | ExecStart=/usr/bin/meminfo-writer 30000 100000 10 | RemainAfterExit=yes 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /qmemman/qubes-meminfo-writer.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Qubes memory information reporter 3 | ConditionPathExists=/run/qubes-service/meminfo-writer 4 | Before=systemd-user-sessions.service 5 | 6 | [Service] 7 | Type=forking 8 | ExecStart=/usr/bin/meminfo-writer 30000 100000 /run/meminfo-writer.pid 9 | PIDFile=/run/meminfo-writer.pid 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /qrexec-lib/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS += -I. -g -O2 -Wall -Wextra -Werror -pie -fPIC -Wmissing-declarations -Wmissing-prototypes 3 | SO_VER=2 4 | LDFLAGS+=-Wl,--no-undefined,--as-needed,-Bsymbolic -L . 5 | .PHONY: all clean install check 6 | objs := ioall.o copy-file.o crc32.o unpack.o pack.o 7 | 8 | pure_lib := libqubes-pure.so 9 | pure_sover := 0 10 | pure_objs := unicode.o qube-name.o 11 | 12 | all: libqubes-rpc-filecopy.so.$(SO_VER) $(pure_lib).$(pure_sover) 13 | libqubes-rpc-filecopy.so.$(SO_VER): $(objs) ./$(pure_lib).$(pure_sover) 14 | $(CC) -shared $(LDFLAGS) -Wl,-soname,$@ -o $@ $^ 15 | validator-test: validator-test.o ./$(pure_lib).$(pure_sover) 16 | libs=$$(pkg-config --libs icu-uc) && $(CC) '-Wl,-rpath,$$ORIGIN' $(LDFLAGS) -o $@ $^ $$libs 17 | $(pure_objs): CFLAGS += -fvisibility=hidden -DQUBES_PURE_IMPLEMENTATION 18 | ifeq ($(CHECK_UNREACHABLE),1) 19 | $(pure_objs): CFLAGS += -DCHECK_UNREACHABLE 20 | endif 21 | validator-test: CFLAGS += -UNDEBUG -std=gnu17 22 | check: validator-test 23 | LD_LIBRARY_PATH=. ./validator-test 24 | 25 | $(pure_lib).$(pure_sover): $(pure_objs) 26 | $(CC) -shared $(LDFLAGS) -Wl,-Bsymbolic,-soname,$@ -o $@ $^ 27 | $(pure_lib): 28 | ln -s $(pure_lib).$(pure_sover) $(pure_lib) 29 | unicode-generator: unicode-generator.o 30 | libs=$$(pkg-config --libs icu-uc) && $(CC) $(LDFLAGS) -o $@ $^ $$libs 31 | %.o: %.c Makefile 32 | $(CC) $(CFLAGS) -MD -MP -MF $@.dep -c -o $@ $< 33 | 34 | unicode.o: unicode-allowlist-table.c 35 | unicode.o: CFLAGS += $(shell pkg-config --cflags icu-uc) 36 | ifeq ($(NO_REBUILD_TABLE),) 37 | unicode-allowlist-table.c: unicode-generator 38 | ./unicode-generator > $@.tmp 39 | mv -- $@.tmp $@ 40 | else ifneq ($(NO_REBUILD_TABLE),1) 41 | $(error NO_REBUILD_TABLE should be empty or 1) 42 | endif 43 | check-table-up-to-date: unicode-generator unicode-allowlist-table.c 44 | ifneq ($(NO_REBUILD_TABLE),1) 45 | $(error check-table-up-to-date without NO_REBUILD_TABLE=1 makes no sense) 46 | endif 47 | /bin/bash -euc 'set -o pipefail; ./unicode-generator | diff -u ./unicode-allowlist-table.c -' 48 | .PHONY: check-table-up-to-date 49 | 50 | %.a: $(objs) 51 | $(AR) rcs $@ $^ 52 | clean: 53 | rm -f ./*.o ./*~ ./*.a ./*.so.* ./*.dep unicode-allowlist-table.c.tmp 54 | 55 | install: 56 | mkdir -p $(DESTDIR)$(LIBDIR) 57 | install libqubes-rpc-filecopy.so.$(SO_VER) $(DESTDIR)$(LIBDIR) 58 | ln -sf libqubes-rpc-filecopy.so.$(SO_VER) $(DESTDIR)$(LIBDIR)/libqubes-rpc-filecopy.so 59 | install $(pure_lib).$(pure_sover) $(DESTDIR)$(LIBDIR) 60 | ln -sf $(pure_lib).$(pure_sover) $(DESTDIR)$(LIBDIR)/$(pure_lib) 61 | mkdir -p $(DESTDIR)$(INCLUDEDIR)/qubes 62 | cp libqubes-rpc-filecopy.h $(DESTDIR)$(INCLUDEDIR) 63 | cp pure.h $(DESTDIR)$(INCLUDEDIR)/qubes 64 | -include ./*.o.dep 65 | -------------------------------------------------------------------------------- /qrexec-lib/copy-file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ioall.h" 3 | #include "libqubes-rpc-filecopy.h" 4 | #include "crc32.h" 5 | 6 | notify_progress_t *notify_progress_func = NULL; 7 | void register_notify_progress(notify_progress_t *func) 8 | { 9 | notify_progress_func = func; 10 | } 11 | 12 | int copy_file(int outfd, int infd, long long size, unsigned long *crc32) 13 | { 14 | char buf[4096]; 15 | long long written = 0; 16 | int ret; 17 | int count; 18 | while (written < size) { 19 | if (size - written > (int)sizeof(buf)) 20 | count = sizeof buf; 21 | else 22 | count = size - written; 23 | ret = read(infd, buf, count); 24 | if (!ret) 25 | return COPY_FILE_READ_EOF; 26 | if (ret < 0) 27 | return COPY_FILE_READ_ERROR; 28 | /* acumulate crc32 if requested */ 29 | if (crc32) 30 | *crc32 = Crc32_ComputeBuf(*crc32, buf, ret); 31 | if (!write_all(outfd, buf, ret)) 32 | return COPY_FILE_WRITE_ERROR; 33 | if (notify_progress_func != NULL) 34 | notify_progress_func(ret, 0); 35 | written += ret; 36 | } 37 | return COPY_FILE_OK; 38 | } 39 | 40 | const char * copy_file_status_to_str(int status) 41 | { 42 | switch (status) { 43 | case COPY_FILE_OK: return "OK"; 44 | case COPY_FILE_READ_EOF: return "Unexpected end of data while reading"; 45 | case COPY_FILE_READ_ERROR: return "Error reading"; 46 | case COPY_FILE_WRITE_ERROR: return "Error writing"; 47 | default: return "????????"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /qrexec-lib/crc32.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------*\ 2 | * CRC-32 version 2.0.0 by Craig Bruce, 2006-04-29. 3 | * 4 | * This program generates the CRC-32 values for the files named in the 5 | * command-line arguments. These are the same CRC-32 values used by GZIP, 6 | * PKZIP, and ZMODEM. The Crc32_ComputeBuf() can also be detached and 7 | * used independently. 8 | * 9 | * THIS PROGRAM IS PUBLIC-DOMAIN SOFTWARE. 10 | * 11 | * Based on the byte-oriented implementation "File Verification Using CRC" 12 | * by Mark R. Nelson in Dr. Dobb's Journal, May 1992, pp. 64-67. 13 | * 14 | * v1.0.0: original release. 15 | * v1.0.1: fixed printf formats. 16 | * v1.0.2: fixed something else. 17 | * v1.0.3: replaced CRC constant table by generator function. 18 | * v1.0.4: reformatted code, made ANSI C. 1994-12-05. 19 | * v2.0.0: rewrote to use memory buffer & static table, 2006-04-29. 20 | \*----------------------------------------------------------------------------*/ 21 | 22 | #include 23 | #include 24 | 25 | /*----------------------------------------------------------------------------*\ 26 | * Local functions 27 | \*----------------------------------------------------------------------------*/ 28 | 29 | unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, 30 | size_t bufLen ); 31 | 32 | /*----------------------------------------------------------------------------*\ 33 | * NAME: 34 | * Crc32_ComputeBuf() - computes the CRC-32 value of a memory buffer 35 | * DESCRIPTION: 36 | * Computes or accumulates the CRC-32 value for a memory buffer. 37 | * The 'inCrc32' gives a previously accumulated CRC-32 value to allow 38 | * a CRC to be generated for multiple sequential buffer-fuls of data. 39 | * The 'inCrc32' for the first buffer must be zero. 40 | * ARGUMENTS: 41 | * inCrc32 - accumulated CRC-32 value, must be 0 on first call 42 | * buf - buffer to compute CRC-32 value for 43 | * bufLen - number of bytes in buffer 44 | * RETURNS: 45 | * crc32 - computed CRC-32 value 46 | * ERRORS: 47 | * (no errors are possible) 48 | \*----------------------------------------------------------------------------*/ 49 | 50 | unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, 51 | size_t bufLen ) 52 | { 53 | static const unsigned long crcTable[256] = { 54 | 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535, 55 | 0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD, 56 | 0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D, 57 | 0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC, 58 | 0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4, 59 | 0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C, 60 | 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,0x26D930AC, 61 | 0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, 62 | 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB, 63 | 0xB6662D3D,0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F, 64 | 0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB, 65 | 0x086D3D2D,0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E, 66 | 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA, 67 | 0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,0x4DB26158,0x3AB551CE, 68 | 0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A, 69 | 0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, 70 | 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409, 71 | 0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81, 72 | 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739, 73 | 0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8, 74 | 0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,0xF00F9344,0x8708A3D2,0x1E01F268, 75 | 0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0, 76 | 0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8, 77 | 0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, 78 | 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF, 79 | 0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703, 80 | 0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7, 81 | 0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A, 82 | 0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE, 83 | 0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242, 84 | 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6, 85 | 0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, 86 | 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D, 87 | 0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5, 88 | 0x47B2CF7F,0x30B5FFE9,0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605, 89 | 0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94, 90 | 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D }; 91 | unsigned long crc32; 92 | unsigned char *byteBuf; 93 | size_t i; 94 | 95 | /** accumulate crc32 for buffer **/ 96 | crc32 = inCrc32 ^ 0xFFFFFFFF; 97 | byteBuf = (unsigned char*) buf; 98 | for (i=0; i < bufLen; i++) { 99 | crc32 = (crc32 >> 8) ^ crcTable[ (crc32 ^ byteBuf[i]) & 0xFF ]; 100 | } 101 | return( crc32 ^ 0xFFFFFFFF ); 102 | } 103 | 104 | /*----------------------------------------------------------------------------*\ 105 | * END OF MODULE: crc32.c 106 | \*----------------------------------------------------------------------------*/ 107 | -------------------------------------------------------------------------------- /qrexec-lib/crc32.h: -------------------------------------------------------------------------------- 1 | #ifndef _CRC32_H 2 | #define _CRC32_H 3 | 4 | extern unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, 5 | size_t bufLen ); 6 | 7 | #endif /* _CRC32_H */ 8 | -------------------------------------------------------------------------------- /qrexec-lib/ioall.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The Qubes OS Project, http://www.qubes-os.org 3 | * 4 | * Copyright (C) 2010 Rafal Wojtczuk 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | * 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "libqubes-rpc-filecopy.h" 28 | 29 | static void perror_wrapper(const char * msg) 30 | { 31 | int prev=errno; 32 | perror(msg); 33 | errno=prev; 34 | } 35 | 36 | void set_nonblock(int fd) 37 | { 38 | int fl = fcntl(fd, F_GETFL, 0); 39 | if (fl & O_NONBLOCK) 40 | return; 41 | fcntl(fd, F_SETFL, fl | O_NONBLOCK); 42 | } 43 | 44 | void set_block(int fd) 45 | { 46 | int fl = fcntl(fd, F_GETFL, 0); 47 | if (!(fl & O_NONBLOCK)) 48 | return; 49 | fcntl(fd, F_SETFL, fl & ~O_NONBLOCK); 50 | } 51 | 52 | int write_all(int fd, const void *buf, int size) 53 | { 54 | int written = 0; 55 | int ret; 56 | while (written < size) { 57 | ret = write(fd, (char *) buf + written, size - written); 58 | if (ret == -1 && errno == EINTR) 59 | continue; 60 | if (ret <= 0) { 61 | return 0; 62 | } 63 | written += ret; 64 | } 65 | // fprintf(stderr, "sent %d bytes\n", size); 66 | return 1; 67 | } 68 | 69 | int read_all(int fd, void *buf, int size) 70 | { 71 | int got_read = 0; 72 | int ret; 73 | while (got_read < size) { 74 | ret = read(fd, (char *) buf + got_read, size - got_read); 75 | if (ret == -1 && errno == EINTR) 76 | continue; 77 | if (ret == 0) { 78 | errno = 0; 79 | fprintf(stderr, "EOF\n"); 80 | return 0; 81 | } 82 | if (ret < 0) { 83 | if (errno != EAGAIN) 84 | perror_wrapper("read"); 85 | return 0; 86 | } 87 | if (got_read == 0) { 88 | // force blocking operation on further reads 89 | set_block(fd); 90 | } 91 | got_read += ret; 92 | } 93 | // fprintf(stderr, "read %d bytes\n", size); 94 | return 1; 95 | } 96 | 97 | int copy_fd_all(int fdout, int fdin) 98 | { 99 | int ret; 100 | char buf[4096]; 101 | for (;;) { 102 | ret = read(fdin, buf, sizeof(buf)); 103 | if (ret == -1 && errno == EINTR) 104 | continue; 105 | if (!ret) 106 | break; 107 | if (ret < 0) { 108 | perror_wrapper("read"); 109 | return 0; 110 | } 111 | if (!write_all(fdout, buf, ret)) { 112 | perror_wrapper("write"); 113 | return 0; 114 | } 115 | } 116 | return 1; 117 | } 118 | -------------------------------------------------------------------------------- /qrexec-lib/ioall.h: -------------------------------------------------------------------------------- 1 | int write_all(int fd, const void *buf, int size); 2 | int read_all(int fd, void *buf, int size); 3 | int copy_fd_all(int fdout, int fdin); 4 | void set_nonblock(int fd); 5 | void set_block(int fd); 6 | -------------------------------------------------------------------------------- /qrexec-lib/libqubes-rpc-filecopy.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The Qubes OS Project, http://www.qubes-os.org 3 | * 4 | * Copyright (C) 2010 Rafal Wojtczuk 5 | * Copyright (C) 2013 Marek Marczykowski 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | * 21 | */ 22 | 23 | #ifndef _LIBQUBES_RPC_FILECOPY_H 24 | #define _LIBQUBES_RPC_FILECOPY_H 25 | 26 | #define FILECOPY_VMNAME_SIZE 32 27 | #define PROGRESS_NOTIFY_DELTA (15*1000*1000) 28 | #define MAX_PATH_LENGTH 16384 29 | 30 | #define LEGAL_EOF 31415926 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | struct file_header { 38 | uint32_t namelen; 39 | uint32_t mode; 40 | uint64_t filelen; 41 | uint32_t atime; 42 | uint32_t atime_nsec; 43 | uint32_t mtime; 44 | uint32_t mtime_nsec; 45 | }; 46 | 47 | struct result_header { 48 | uint32_t error_code; 49 | uint32_t _pad; 50 | uint64_t crc32; 51 | } __attribute__((packed)); 52 | 53 | /* optional info about last processed file */ 54 | struct result_header_ext { 55 | uint32_t last_namelen; 56 | char last_name[]; 57 | } __attribute__((packed)); 58 | 59 | enum { 60 | COPY_FILE_OK, 61 | COPY_FILE_READ_EOF, 62 | COPY_FILE_READ_ERROR, 63 | COPY_FILE_WRITE_ERROR 64 | }; 65 | 66 | enum copy_flags { 67 | COPY_DEFAULT = 0, 68 | COPY_ALLOW_SYMLINKS = (1 << 0), 69 | COPY_ALLOW_DIRECTORIES = (1 << 1), 70 | COPY_ALLOW_UNSAFE_CHARACTERS = (1 << 2), 71 | COPY_ALLOW_NON_CANONICAL_SYMLINKS = (1 << 3), 72 | COPY_ALLOW_UNSAFE_SYMLINKS = (1 << 4), 73 | }; 74 | 75 | /* feedback handling */ 76 | typedef void (notify_progress_t)(int, int); 77 | typedef void (error_handler_t)(const char *fmt, va_list args); 78 | void register_notify_progress(notify_progress_t *func); 79 | void register_error_handler(error_handler_t *func); 80 | 81 | /* common functions */ 82 | int copy_file(int outfd, int infd, long long size, unsigned long *crc32); 83 | const char *copy_file_status_to_str(int status); 84 | void set_size_limit(unsigned long long new_bytes_limit, unsigned long long new_files_limit); 85 | void set_verbose(int value); 86 | /* 87 | * Delay extracting a file if there is no enough space for it - wait for space 88 | * for this file, plus a given margin. 89 | */ 90 | void set_wait_for_space(unsigned long margin); 91 | /* register open fd to /proc/PID/fd of this process */ 92 | void set_procfs_fd(int value); 93 | int write_all(int fd, const void *buf, int size); 94 | int read_all(int fd, void *buf, int size); 95 | int copy_fd_all(int fdout, int fdin); 96 | void set_nonblock(int fd); 97 | void set_block(int fd); 98 | 99 | /* unpacking */ 100 | extern unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, 101 | size_t bufLen ); 102 | extern int do_unpack(void); 103 | extern int do_unpack_ext(int flags); 104 | 105 | /* packing */ 106 | int single_file_processor(const char *filename, const struct stat *st); 107 | int do_fs_walk(const char *file, int ignore_symlinks); 108 | /* used in tar2qfile to alter only headers, but keep original file stream */ 109 | void write_headers(const struct file_header *hdr, const char *filename); 110 | int copy_file_with_crc(int outfd, int infd, long long size); 111 | /* MUST be called before first do_fs_walk/single_file_processor */ 112 | void qfile_pack_init(void); 113 | void set_ignore_quota_error(int value); 114 | /* those two will call registered error handler if needed */ 115 | void wait_for_result(void); 116 | void notify_end_and_wait_for_result(void); 117 | 118 | #endif /* _LIBQUBES_RPC_FILECOPY_H */ 119 | -------------------------------------------------------------------------------- /qrexec-lib/pack.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE /* See feature_test_macros(7) */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "libqubes-rpc-filecopy.h" 13 | 14 | static unsigned long crc32_sum; 15 | static int ignore_quota_error = 0; 16 | error_handler_t *error_handler = NULL; 17 | 18 | void register_error_handler(error_handler_t *value) { 19 | error_handler = value; 20 | } 21 | 22 | _Noreturn static void call_error_handler(const char *fmt, ...) 23 | { 24 | va_list args; 25 | va_start(args, fmt); 26 | if (error_handler) { 27 | error_handler(fmt, args); 28 | } else { 29 | vfprintf(stderr, fmt, args); 30 | } 31 | va_end(args); 32 | exit(1); 33 | } 34 | 35 | static int write_all_with_crc(int fd, const void *buf, int size) 36 | { 37 | crc32_sum = Crc32_ComputeBuf(crc32_sum, buf, size); 38 | return write_all(fd, buf, size); 39 | } 40 | 41 | void notify_end_and_wait_for_result(void) 42 | { 43 | struct file_header end_hdr; 44 | 45 | /* notify end of transfer */ 46 | memset(&end_hdr, 0, sizeof(end_hdr)); 47 | end_hdr.namelen = 0; 48 | end_hdr.filelen = 0; 49 | write_all_with_crc(1, &end_hdr, sizeof(end_hdr)); 50 | 51 | set_block(0); 52 | wait_for_result(); 53 | } 54 | 55 | static void sanitize_remote_filename(char *untrusted_filename) 56 | { 57 | for (; *untrusted_filename; ++untrusted_filename) { 58 | if (*untrusted_filename < ' ' || 59 | *untrusted_filename > '~' || 60 | *untrusted_filename == '"') 61 | *untrusted_filename = '_'; 62 | } 63 | } 64 | 65 | void wait_for_result(void) 66 | { 67 | struct result_header hdr; 68 | struct result_header_ext hdr_ext; 69 | char last_filename[MAX_PATH_LENGTH + 1]; 70 | char last_filename_prefix[] = "; Last file: "; 71 | 72 | if (!read_all(0, &hdr, sizeof(hdr))) { 73 | if (errno == EAGAIN) { 74 | // no result sent and stdin still open 75 | return; 76 | } else { 77 | // other read error or EOF 78 | exit(1); // hopefully remote has produced error message 79 | } 80 | } 81 | if (!read_all(0, &hdr_ext, sizeof(hdr_ext))) { 82 | // remote used old result_header struct 83 | hdr_ext.last_namelen = 0; 84 | } 85 | if (hdr_ext.last_namelen > MAX_PATH_LENGTH) { 86 | // read only at most MAX_PATH_LENGTH chars 87 | hdr_ext.last_namelen = MAX_PATH_LENGTH; 88 | } 89 | if (!read_all(0, last_filename, hdr_ext.last_namelen)) { 90 | fprintf(stderr, "Failed to get last filename\n"); 91 | hdr_ext.last_namelen = 0; 92 | } 93 | last_filename[hdr_ext.last_namelen] = '\0'; 94 | if (!hdr_ext.last_namelen) 95 | /* set prefix to empty string */ 96 | last_filename_prefix[0] = '\0'; 97 | 98 | /* sanitize the remote filename */ 99 | sanitize_remote_filename(last_filename); 100 | 101 | errno = hdr.error_code; 102 | if (hdr.error_code != 0) { 103 | switch (hdr.error_code) { 104 | case EEXIST: 105 | call_error_handler("A file named \"%s\" already exists in QubesIncoming dir", last_filename); 106 | break; 107 | case EINVAL: 108 | call_error_handler("File copy: Corrupted data from packer%s\"%s\"", last_filename_prefix, last_filename); 109 | break; 110 | case EILSEQ: 111 | call_error_handler("Forbidden character in file or link target. Name might look somewhat like \"%s\"", last_filename); 112 | break; 113 | case ENOLINK: 114 | // FIXME: the protocol only provides the name of the file, not what it points to. 115 | // FIXME: This assumes that a symlink target was rejected, not a path. However, 116 | // this code should only produces valid paths, so if an invalid path gets sent, 117 | // that's a bug. 118 | call_error_handler("Cannot verify that link at \"%s\" would not be broken by copy", last_filename); 119 | break; 120 | case EDQUOT: 121 | if (ignore_quota_error) { 122 | /* skip also CRC check as sender and receiver might be 123 | * desynchronized in this case */ 124 | return; 125 | } 126 | /* fallthrough */ 127 | default: 128 | call_error_handler("File copy: \"%s%s%s\"", 129 | strerror(hdr.error_code), last_filename_prefix, last_filename); 130 | } 131 | } 132 | if (hdr.crc32 != crc32_sum) { 133 | call_error_handler("File transfer failed: checksum mismatch"); 134 | } 135 | } 136 | 137 | void write_headers(const struct file_header *hdr, const char *filename) 138 | { 139 | if (!write_all_with_crc(1, hdr, sizeof(*hdr)) 140 | || !write_all_with_crc(1, filename, hdr->namelen)) { 141 | set_block(0); 142 | wait_for_result(); 143 | exit(1); 144 | } 145 | } 146 | 147 | int copy_file_with_crc(int outfd, int infd, long long size) { 148 | return copy_file(outfd, infd, size, &crc32_sum); 149 | } 150 | 151 | int single_file_processor(const char *filename, const struct stat *st) 152 | { 153 | struct file_header hdr; 154 | int fd; 155 | mode_t mode = st->st_mode; 156 | 157 | hdr.namelen = strlen(filename) + 1; 158 | hdr.mode = mode; 159 | hdr.atime = st->st_atim.tv_sec; 160 | hdr.atime_nsec = st->st_atim.tv_nsec; 161 | hdr.mtime = st->st_mtim.tv_sec; 162 | hdr.mtime_nsec = st->st_mtim.tv_nsec; 163 | 164 | if (S_ISREG(mode)) { 165 | int ret; 166 | fd = open(filename, O_RDONLY); 167 | if (fd < 0) 168 | call_error_handler("open %s", filename); 169 | hdr.filelen = st->st_size; 170 | write_headers(&hdr, filename); 171 | ret = copy_file(1, fd, hdr.filelen, &crc32_sum); 172 | if (ret != COPY_FILE_OK) { 173 | if (ret != COPY_FILE_WRITE_ERROR) 174 | call_error_handler("Copying file %s: %s", filename, 175 | copy_file_status_to_str(ret)); 176 | else { 177 | set_block(0); 178 | wait_for_result(); 179 | exit(1); 180 | } 181 | } 182 | close(fd); 183 | } 184 | if (S_ISDIR(mode)) { 185 | hdr.filelen = 0; 186 | write_headers(&hdr, filename); 187 | } 188 | if (S_ISLNK(mode)) { 189 | char name[st->st_size + 1]; 190 | if (readlink(filename, name, sizeof(name)) != st->st_size) 191 | call_error_handler("readlink %s", filename); 192 | hdr.filelen = st->st_size; 193 | write_headers(&hdr, filename); 194 | if (!write_all_with_crc(1, name, st->st_size)) { 195 | set_block(0); 196 | wait_for_result(); 197 | exit(1); 198 | } 199 | } 200 | // check for possible error from qfile-unpacker 201 | wait_for_result(); 202 | return 0; 203 | } 204 | 205 | int do_fs_walk(const char *file, int ignore_symlinks) 206 | { 207 | char *newfile; 208 | struct stat st; 209 | struct dirent *ent; 210 | DIR *dir; 211 | 212 | if (lstat(file, &st)) 213 | call_error_handler("stat %s", file); 214 | if (S_ISLNK(st.st_mode) && ignore_symlinks) 215 | return 0; 216 | single_file_processor(file, &st); 217 | if (!S_ISDIR(st.st_mode)) 218 | return 0; 219 | dir = opendir(file); 220 | if (!dir) 221 | call_error_handler("opendir %s", file); 222 | while ((ent = readdir(dir))) { 223 | char *fname = ent->d_name; 224 | if (!strcmp(fname, ".") || !strcmp(fname, "..")) 225 | continue; 226 | if (asprintf(&newfile, "%s/%s", file, fname) >= 0) { 227 | do_fs_walk(newfile, ignore_symlinks); 228 | free(newfile); 229 | } else { 230 | fprintf(stderr, "asprintf failed\n"); 231 | exit(1); 232 | } 233 | } 234 | closedir(dir); 235 | // directory metadata is resent; this makes the code simple, 236 | // and the atime/mtime is set correctly at the second time 237 | single_file_processor(file, &st); 238 | return 0; 239 | } 240 | 241 | void qfile_pack_init(void) { 242 | crc32_sum = 0; 243 | ignore_quota_error = 0; 244 | // this will allow checking for possible feedback packet in the middle of transfer 245 | set_nonblock(0); 246 | signal(SIGPIPE, SIG_IGN); 247 | error_handler = NULL; 248 | } 249 | 250 | void set_ignore_quota_error(int value) { 251 | ignore_quota_error = value; 252 | } 253 | -------------------------------------------------------------------------------- /qrexec-lib/pure.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The Qubes OS Project, https://www.qubes-os.org 3 | * 4 | * Copyright (C) 2023 Demi Marie Obenour 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifndef QUBES_UTIL_PURE_H 22 | #define QUBES_UTIL_PURE_H QUBES_UTIL_PURE_H 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #else 26 | #include // for bool 27 | #endif 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #if defined QUBES_EXPORT 34 | # error QUBES_EXPORT already defined 35 | #elif defined _WIN32 || defined __CYGWIN__ 36 | # ifdef _MSC_VER 37 | # define QUBES_EXPORT __declspec(dllexport) 38 | # define QUBES_IMPORT __declspec(dllimport) 39 | # elif defined __GNUC__ 40 | # define QUBES_EXPORT __attribute__(dllexport) 41 | # define QUBES_EXPORT __attribute__(dllimport) 42 | # else 43 | # error unknown C compiler for Windows target 44 | # endif 45 | #elif defined __has_attribute 46 | # if __has_attribute(visibility) 47 | # define QUBES_EXPORT __attribute__((visibility("default"))) 48 | # define QUBES_IMPORT __attribute__((visibility("default"))) 49 | # elif defined __clang__ 50 | # error clang always supports visibility 51 | # endif 52 | #elif defined __GNUC__ && __GNUC__ >= 4 53 | # define QUBES_EXPORT __attribute__((visibility("default"))) 54 | # define QUBES_IMPORT __attribute__((visibility("default"))) 55 | #endif 56 | 57 | #ifndef QUBES_EXPORT 58 | # if (defined __clang__) || (defined __GNUC__ && __GNUC__ >= 4) || defined _MSC_VER 59 | # error This compiler should support visibility but does not 60 | # endif 61 | # define QUBES_EXPORT /* nothing */ 62 | # define QUBES_IMPORT /* nothing */ 63 | #endif 64 | 65 | #ifdef QUBES_PURE_IMPLEMENTATION 66 | # define QUBES_PURE_PUBLIC QUBES_EXPORT 67 | #else 68 | # define QUBES_PURE_PUBLIC QUBES_IMPORT 69 | #endif 70 | 71 | /** 72 | * A counted buffer used by some functions in this library. 73 | * Equivalent to Rust's &[T]. 74 | */ 75 | struct QubesSlice { 76 | /// Pointer to the data 77 | const uint8_t *pointer; 78 | /// Length of the data 79 | size_t length; 80 | } __attribute__((__may_alias__)); 81 | 82 | /** A mutable slice. Equivalent to Rust's &mut [T]. */ 83 | struct QubesMutableSlice { 84 | /// Pointer to the data 85 | uint8_t *pointer; 86 | /// Length of the data 87 | size_t length; 88 | } __attribute__((__may_alias__)); 89 | 90 | /** 91 | * Validate that a string is a valid path and will not result in 92 | * directory traversal if used as such. Characters that are unsafe for 93 | * filenames are also rejected, including invalid UTF-8 sequences and 94 | * all control characters. The input must be NUL-terminated. 95 | * 96 | * \param[in] untrusted_path The path to be checked. 97 | * \return \true on success and \false on failure. 98 | * 99 | * This is equivalent to passing zero for the flags parameter 100 | * to qubes_pure_validate_file_name_v2() and checking that 101 | * the result is zero. 102 | */ 103 | QUBES_PURE_PUBLIC bool 104 | qubes_pure_validate_file_name(const uint8_t *untrusted_path); 105 | 106 | /** 107 | * Validate that a string is a valid path and will not result in 108 | * directory traversal if used as such. If flags does not include 109 | * \ref QUBES_PURE_ALLOW_UNSAFE_CHARACTERS, characters that are unsafe for 110 | * filenames are also rejected, including invalid UTF-8 sequences and 111 | * all control characters. The input must be NUL-terminated. 112 | * 113 | * \param[in] untrusted_path The path to be checked. 114 | * \param flags 0 or QUBES_PURE_ALLOW_UNSAFE_CHARACTERS. 115 | * \return 0 on success, negative errno value on failure. 116 | */ 117 | QUBES_PURE_PUBLIC int 118 | qubes_pure_validate_file_name_v2(const uint8_t *const untrusted_path, 119 | const uint32_t flags); 120 | 121 | /** 122 | * Validate that `untrusted_name` is a valid symbolic link name 123 | * and that creating a symbolic link with that name and target 124 | * `untrusted_target` is also safe. Characters that are unsafe for 125 | * filenames are also rejected, including invalid UTF-8 126 | * sequences and all control characters. The input must be 127 | * NUL-terminated. 128 | * 129 | * Returns true on success and false on failure. 130 | * 131 | * This is equivalent to passing zero for the flags parameter 132 | * to qubes_pure_validate_symbolic_link_v2() and checking that 133 | * the result is zero. 134 | */ 135 | QUBES_PURE_PUBLIC bool 136 | qubes_pure_validate_symbolic_link(const uint8_t *untrusted_name, 137 | const uint8_t *untrusted_target); 138 | 139 | /** 140 | * Validate that `untrusted_path` is a valid symbolic link name 141 | * and that creating a symbolic link with that name and target 142 | * `untrusted_target` is also safe. If flags does not include 143 | * \ref QUBES_PURE_ALLOW_UNSAFE_CHARACTERS, characters that are unsafe for 144 | * filenames are also rejected, including invalid UTF-8 sequences and 145 | * all control characters. The input must be NUL-terminated. 146 | * 147 | * \param[in] untrusted_path The path to be checked. 148 | * \param[in] untrusted_target The proposed target for the symbolic link. 149 | * \param flags 0 or QUBES_PURE_ALLOW_UNSAFE_CHARACTERS. 150 | * \return 0 on success, negative errno value on failure. 151 | */ 152 | QUBES_PURE_PUBLIC int 153 | qubes_pure_validate_symbolic_link_v2(const uint8_t *untrusted_path, 154 | const uint8_t *untrusted_target, 155 | uint32_t flags); 156 | 157 | 158 | /** 159 | * Validate that `code_point` is safe to display. To be considered safe to 160 | * display, a code point must be valid and not be a control character. 161 | * Additionally, the code point must not be a character that is complex 162 | * to render and thus significantly increases the attack surface of text 163 | * rendering libraries such as Fribidi, Harfbuzz, or Pango. The set of 164 | * characters that are considered complex to render is implementation 165 | * dependent and may change in future versions of this library. Currenty, 166 | * it includes the following: 167 | * 168 | * - Characters that have a character direction property other than 169 | * WHITE_SPACE_NEUTRAL, OTHRE_NEUTRAL, EUROPEAN_NUMBER_TERMINATOR, 170 | * EUROPEAN_NUMBER_SEPARATOR, COMMON_NUMBER_SEPARATOR, or LEFT_TO_RIGHT. 171 | * 172 | * - Characters that are part of scripts that are not recommended for use 173 | * in identifiers. This includes limited-use scripts. 174 | * 175 | * - Characters with scripts that are not INHERITED, CYRILLIC, GREEK, LATIN, 176 | * BRAILLE, SIMPLIFIED_HAN, TRADITIONAL_HAN, HAN, HAN_WITH_BOPOMOFO, JAMO, 177 | * HANGUL, BOPOMOFO, KATAKANA_OR_HIRAGANA, HIRIGANA, KATAKANA, JAPANESE, 178 | * KOREAN, or COMMON. 179 | * 180 | * This is implemented as an allowlist, not as a blocklist, so unknown code 181 | * points are considered _unsafe_ for display. 182 | * 183 | * @param code_point The code point to check for being safe to display. 184 | * 185 | * This API does _not_ require that @p code_point is a valid Unicode code point. 186 | * Invalid code points are simply considered to be unsafe for display. 187 | * Therefore, this function has defined behavior for _all_ inputs. 188 | */ 189 | QUBES_PURE_PUBLIC bool 190 | qubes_pure_code_point_safe_for_display(uint32_t code_point); 191 | 192 | /** 193 | * Validate that `untrusted_str` is safe to display. To be considered safe to 194 | * display, a string must be valid UTF-8 and contain no control characters 195 | * except perhaps newline. The string must also contain no characters that 196 | * are considered unsafe for display by qubes_pure_code_point_safe_for_display(). 197 | */ 198 | QUBES_PURE_PUBLIC bool 199 | qubes_pure_string_safe_for_display(const char *untrusted_str, 200 | size_t line_length); 201 | 202 | /** Initialize a QubesSlice from a nul-terminated string. */ 203 | static inline struct QubesSlice 204 | qubes_pure_buffer_init_from_nul_terminated_string(const char *str) 205 | { 206 | if (str == NULL) 207 | abort(); 208 | return (struct QubesSlice) { 209 | .pointer = (const uint8_t *)str, 210 | .length = strlen(str), 211 | }; 212 | } 213 | 214 | /** Minimum length of a Qube name */ 215 | #define QUBES_PURE_MIN_QUBE_NAME_LEN 1 216 | 217 | /** Maximum length of a Qube name */ 218 | #define QUBES_PURE_MAX_QUBE_NAME_LEN 31 219 | 220 | /** 221 | * Errors that can occur when validating a qube name. 222 | */ 223 | enum QubeNameValidationError { 224 | /// Qube name is OK (not an error). 225 | QUBE_NAME_OK = 0, 226 | /// Name is empty. 227 | QUBE_NAME_EMPTY = -1, 228 | /// Name is more than QUBES_PURE_MAX_QUBE_NAME_LEN bytes. 229 | QUBE_NAME_TOO_LONG = -2, 230 | /// Name does not start with an ASCII letter. 231 | QUBE_NAME_INVALID_FIRST_CHARACTER = -3, 232 | /// Invalid character in name. 233 | QUBE_NAME_INVALID_SUBSEQUENT_CHARACTER = -4, 234 | /// Name is `none`, `default`, `Domain-0`, or ends in `-dm`. 235 | /// These names are reserved. 236 | QUBE_NAME_RESERVED = -6, 237 | }; 238 | 239 | /** 240 | * Validate that `untrusted_str` is a valid qube name. A valid qube name 241 | * must: 242 | * 243 | * - Have a length between 1 and 31 bytes (inclusive). 244 | * - Consist only of characters matching the glob pattern [A-Za-z0-9_.-]. 245 | * - Begin with an uppercase or lowercase ASCII letter. 246 | * - Not end with the 3 bytes `-dm`, to avoid confusing with device-model 247 | * stubdomains. 248 | * - Not be `none` or `default`, which are reserved for the admin API. 249 | * - Not be `Domain-0`, which is used by libvirt and libxl to refer to dom0. 250 | * 251 | * Returns QUBE_NAME_OK (0) on success and something else on failure. 252 | */ 253 | QUBES_PURE_PUBLIC enum QubeNameValidationError 254 | qubes_pure_is_valid_qube_name(const struct QubesSlice untrusted_str); 255 | 256 | /// Flags for pathname validation functions. 257 | enum QubesFilenameValidationFlags { 258 | /// Disable Unicode charset restrictions and UTF-8 validity checks. 259 | QUBES_PURE_ALLOW_UNSAFE_CHARACTERS = (1 << 0), 260 | /// Allow non-canonical symbolic links. Paths are still checked 261 | /// to be canonial, as they are assumed to come from a filesystem 262 | /// traversal, which will always produce canonical paths. 263 | QUBES_PURE_ALLOW_NON_CANONICAL_SYMLINKS = (1 << 1), 264 | /// Do not check symbolic links at all. 265 | QUBES_PURE_ALLOW_UNSAFE_SYMLINKS = (1 << 2), 266 | /// Allow all paths to be non-canonical, including symlinks. 267 | QUBES_PURE_ALLOW_NON_CANONICAL_PATHS = (1 << 3), 268 | /// Allow trailing slash. 269 | QUBES_PURE_ALLOW_TRAILING_SLASH = (1 << 4), 270 | }; 271 | 272 | #ifdef __cplusplus 273 | } 274 | #endif 275 | #endif // !defined QUBES_UTIL_PURE_H 276 | -------------------------------------------------------------------------------- /qrexec-lib/qube-name.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pure.h" 3 | 4 | QUBES_PURE_PUBLIC enum QubeNameValidationError 5 | qubes_pure_is_valid_qube_name(const struct QubesSlice untrusted_name) 6 | { 7 | /* Check the length first. */ 8 | if (untrusted_name.length < QUBES_PURE_MIN_QUBE_NAME_LEN) 9 | return QUBE_NAME_EMPTY; 10 | if (untrusted_name.length > QUBES_PURE_MAX_QUBE_NAME_LEN) 11 | return QUBE_NAME_TOO_LONG; 12 | size_t const length = untrusted_name.length; 13 | switch (untrusted_name.pointer[0]) { 14 | case 'a' ... 'z': 15 | case 'A' ... 'Z': 16 | break; 17 | default: 18 | return QUBE_NAME_INVALID_FIRST_CHARACTER; /* bad first character */ 19 | } 20 | 21 | /* Check all other characters */ 22 | for (size_t i = 1; i < length; ++i) { 23 | switch (untrusted_name.pointer[i]) { 24 | case 'a' ... 'z': 25 | case 'A' ... 'Z': 26 | case '0' ... '9': 27 | case '_': 28 | case '.': 29 | case '-': 30 | break; 31 | default: 32 | /* Bad character */ 33 | return QUBE_NAME_INVALID_SUBSEQUENT_CHARACTER; 34 | } 35 | } 36 | 37 | /* 38 | * All special cases are for names with at least 4 bytes, 39 | * except for the literal string "-dm" which is already rejected 40 | * due to beginning with "-". Therefore, any string of length 41 | * 3 or less that has not already been rejected is valid. 42 | */ 43 | if (length < 4) 44 | return QUBE_NAME_OK; 45 | 46 | /* 47 | * A name ending in "-dm" could be the name of a stubdomain, so it is 48 | * disallowed. 49 | */ 50 | if (memcmp(untrusted_name.pointer + (length - 3), "-dm", 3) == 0) 51 | return QUBE_NAME_RESERVED; 52 | 53 | switch (length) { 54 | case 4: 55 | /* "none" is reserved for use by the Admin API. */ 56 | return memcmp(untrusted_name.pointer, "none", 4) == 0 ? 57 | QUBE_NAME_RESERVED : QUBE_NAME_OK; 58 | case 7: 59 | /* "default" is reserved for use by the Admin API. */ 60 | return memcmp(untrusted_name.pointer, "default", 7) == 0 ? 61 | QUBE_NAME_RESERVED : QUBE_NAME_OK; 62 | case 8: 63 | /* "Domain-0" is used by libxl to refer to dom0. */ 64 | return memcmp(untrusted_name.pointer, "Domain-0", 8) == 0 ? 65 | QUBE_NAME_RESERVED : QUBE_NAME_OK; 66 | default: 67 | /* Anything else is valid! */ 68 | return QUBE_NAME_OK; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /qrexec-lib/unicode-allowlist-table.c: -------------------------------------------------------------------------------- 1 | case 0x000020 ... 0x00007e: 2 | case 0x0000a1 ... 0x0000a5: 3 | case 0x0000a7: 4 | case 0x0000aa ... 0x0000ac: 5 | case 0x0000b1 ... 0x0000b3: 6 | case 0x0000b5 ... 0x0000b7: 7 | case 0x0000b9 ... 0x0002c1: 8 | case 0x0002c6 ... 0x0002d1: 9 | case 0x0002e0 ... 0x0002e4: 10 | case 0x0002ec: 11 | case 0x0002ee: 12 | case 0x000370 ... 0x000374: 13 | case 0x000376 ... 0x000377: 14 | case 0x00037a ... 0x00037f: 15 | case 0x000386 ... 0x00038a: 16 | case 0x00038c: 17 | case 0x00038e ... 0x0003a1: 18 | case 0x0003a3 ... 0x0003e1: 19 | case 0x0003f0 ... 0x000481: 20 | case 0x00048a ... 0x00052f: 21 | case 0x00060c: 22 | case 0x000964 ... 0x000965: 23 | case 0x000e3f: 24 | case 0x0010fb: 25 | case 0x001100 ... 0x0011ff: 26 | case 0x0016eb ... 0x0016ed: 27 | case 0x001735 ... 0x001736: 28 | case 0x001802 ... 0x001803: 29 | case 0x001805: 30 | case 0x001c80 ... 0x001c88: 31 | case 0x001cd3: 32 | case 0x001ce9 ... 0x001cec: 33 | case 0x001cee ... 0x001cf3: 34 | case 0x001cf5 ... 0x001cf6: 35 | case 0x001cfa: 36 | case 0x001d00 ... 0x001dbf: 37 | case 0x001e00 ... 0x001f15: 38 | case 0x001f18 ... 0x001f1d: 39 | case 0x001f20 ... 0x001f45: 40 | case 0x001f48 ... 0x001f4d: 41 | case 0x001f50 ... 0x001f57: 42 | case 0x001f59: 43 | case 0x001f5b: 44 | case 0x001f5d: 45 | case 0x001f5f ... 0x001f7d: 46 | case 0x001f80 ... 0x001fb4: 47 | case 0x001fb6 ... 0x001fbc: 48 | case 0x001fbe: 49 | case 0x001fc2 ... 0x001fc4: 50 | case 0x001fc6 ... 0x001fcc: 51 | case 0x001fd0 ... 0x001fd3: 52 | case 0x001fd6 ... 0x001fdb: 53 | case 0x001fe0 ... 0x001fec: 54 | case 0x001ff2 ... 0x001ff4: 55 | case 0x001ff6 ... 0x001ffc: 56 | case 0x002010 ... 0x002027: 57 | case 0x002030 ... 0x00205e: 58 | case 0x002070 ... 0x002071: 59 | case 0x002074 ... 0x00208e: 60 | case 0x002090 ... 0x00209c: 61 | case 0x0020a0 ... 0x0020c0: 62 | case 0x002102: 63 | case 0x002107: 64 | case 0x00210a ... 0x002113: 65 | case 0x002115: 66 | case 0x002118 ... 0x00211d: 67 | case 0x002124: 68 | case 0x002126: 69 | case 0x002128: 70 | case 0x00212a ... 0x00212d: 71 | case 0x00212f ... 0x002139: 72 | case 0x00213c ... 0x002149: 73 | case 0x00214b: 74 | case 0x00214e: 75 | case 0x002150 ... 0x002189: 76 | case 0x002190 ... 0x002194: 77 | case 0x00219a ... 0x00219b: 78 | case 0x0021a0: 79 | case 0x0021a3: 80 | case 0x0021a6: 81 | case 0x0021ae: 82 | case 0x0021ce ... 0x0021cf: 83 | case 0x0021d2: 84 | case 0x0021d4: 85 | case 0x0021f4 ... 0x0022ff: 86 | case 0x002308 ... 0x00230b: 87 | case 0x002320 ... 0x002321: 88 | case 0x002329 ... 0x00232a: 89 | case 0x00237c: 90 | case 0x00239b ... 0x0023b3: 91 | case 0x0023dc ... 0x0023e1: 92 | case 0x002460 ... 0x00249b: 93 | case 0x0024ea ... 0x0024ff: 94 | case 0x0025b7: 95 | case 0x0025c1: 96 | case 0x0025f8 ... 0x0025ff: 97 | case 0x00266f: 98 | case 0x002768 ... 0x002793: 99 | case 0x0027c0 ... 0x0027ff: 100 | case 0x002900 ... 0x002aff: 101 | case 0x002b30 ... 0x002b44: 102 | case 0x002b47 ... 0x002b4c: 103 | case 0x002c60 ... 0x002c7f: 104 | case 0x002e00 ... 0x002e4f: 105 | case 0x002e52 ... 0x002e5d: 106 | case 0x003001 ... 0x003003: 107 | case 0x003005 ... 0x003011: 108 | case 0x003014 ... 0x00301f: 109 | case 0x003021 ... 0x003029: 110 | case 0x003030 ... 0x003035: 111 | case 0x003038 ... 0x00303d: 112 | case 0x003041 ... 0x003096: 113 | case 0x00309d ... 0x0030ff: 114 | case 0x003105 ... 0x00312f: 115 | case 0x003131 ... 0x00318e: 116 | case 0x003192 ... 0x003195: 117 | case 0x0031a0 ... 0x0031bf: 118 | case 0x0031f0 ... 0x0031ff: 119 | case 0x003220 ... 0x003229: 120 | case 0x003248 ... 0x00324f: 121 | case 0x003251 ... 0x00325f: 122 | case 0x003280 ... 0x003289: 123 | case 0x0032b1 ... 0x0032bf: 124 | case 0x003400 ... 0x004dbf: 125 | case 0x004e00 ... 0x009fff: 126 | case 0x00a640 ... 0x00a66e: 127 | case 0x00a673: 128 | case 0x00a67e ... 0x00a69d: 129 | case 0x00a717 ... 0x00a71f: 130 | case 0x00a722 ... 0x00a788: 131 | case 0x00a78b ... 0x00a7ca: 132 | case 0x00a7d0 ... 0x00a7d1: 133 | case 0x00a7d3: 134 | case 0x00a7d5 ... 0x00a7d9: 135 | case 0x00a7f2 ... 0x00a7ff: 136 | case 0x00a830 ... 0x00a835: 137 | case 0x00a838: 138 | case 0x00a92e: 139 | case 0x00a960 ... 0x00a97c: 140 | case 0x00a9cf: 141 | case 0x00ab30 ... 0x00ab5a: 142 | case 0x00ab5c ... 0x00ab69: 143 | case 0x00ac00 ... 0x00d7a3: 144 | case 0x00d7b0 ... 0x00d7c6: 145 | case 0x00d7cb ... 0x00d7fb: 146 | case 0x00f900 ... 0x00fa6d: 147 | case 0x00fa70 ... 0x00fad9: 148 | case 0x00fb00 ... 0x00fb06: 149 | case 0x00fd3e ... 0x00fd3f: 150 | case 0x00fe10 ... 0x00fe19: 151 | case 0x00fe30 ... 0x00fe52: 152 | case 0x00fe54 ... 0x00fe66: 153 | case 0x00fe68 ... 0x00fe6b: 154 | case 0x00ff01 ... 0x00ff3d: 155 | case 0x00ff3f: 156 | case 0x00ff41 ... 0x00ffbe: 157 | case 0x00ffc2 ... 0x00ffc7: 158 | case 0x00ffca ... 0x00ffcf: 159 | case 0x00ffd2 ... 0x00ffd7: 160 | case 0x00ffda ... 0x00ffdc: 161 | case 0x00ffe0 ... 0x00ffe2: 162 | case 0x00ffe5 ... 0x00ffe6: 163 | case 0x00ffe9 ... 0x00ffec: 164 | case 0x010100 ... 0x010102: 165 | case 0x010107 ... 0x010133: 166 | case 0x010140 ... 0x010178: 167 | case 0x01018a ... 0x01018b: 168 | case 0x0102e1 ... 0x0102fb: 169 | case 0x010780 ... 0x010785: 170 | case 0x010787 ... 0x0107b0: 171 | case 0x0107b2 ... 0x0107ba: 172 | case 0x016fe2 ... 0x016fe3: 173 | case 0x01aff0 ... 0x01aff3: 174 | case 0x01aff5 ... 0x01affb: 175 | case 0x01affd ... 0x01affe: 176 | case 0x01b000 ... 0x01b122: 177 | case 0x01b132: 178 | case 0x01b150 ... 0x01b152: 179 | case 0x01b155: 180 | case 0x01b164 ... 0x01b167: 181 | case 0x01d2c0 ... 0x01d2d3: 182 | case 0x01d2e0 ... 0x01d2f3: 183 | case 0x01d360 ... 0x01d378: 184 | case 0x01d400 ... 0x01d454: 185 | case 0x01d456 ... 0x01d49c: 186 | case 0x01d49e ... 0x01d49f: 187 | case 0x01d4a2: 188 | case 0x01d4a5 ... 0x01d4a6: 189 | case 0x01d4a9 ... 0x01d4ac: 190 | case 0x01d4ae ... 0x01d4b9: 191 | case 0x01d4bb: 192 | case 0x01d4bd ... 0x01d4c3: 193 | case 0x01d4c5 ... 0x01d505: 194 | case 0x01d507 ... 0x01d50a: 195 | case 0x01d50d ... 0x01d514: 196 | case 0x01d516 ... 0x01d51c: 197 | case 0x01d51e ... 0x01d539: 198 | case 0x01d53b ... 0x01d53e: 199 | case 0x01d540 ... 0x01d544: 200 | case 0x01d546: 201 | case 0x01d54a ... 0x01d550: 202 | case 0x01d552 ... 0x01d6a5: 203 | case 0x01d6a8 ... 0x01d7cb: 204 | case 0x01d7ce ... 0x01d7ff: 205 | case 0x01df00 ... 0x01df1e: 206 | case 0x01df25 ... 0x01df2a: 207 | case 0x01e030 ... 0x01e06d: 208 | case 0x01f100 ... 0x01f10c: 209 | case 0x01fbf0 ... 0x01fbf9: 210 | case 0x020000 ... 0x02a6df: 211 | case 0x02a700 ... 0x02b739: 212 | case 0x02b740 ... 0x02b81d: 213 | case 0x02b820 ... 0x02cea1: 214 | case 0x02ceb0 ... 0x02ebe0: 215 | case 0x02f800 ... 0x02fa1d: 216 | case 0x030000 ... 0x03134a: 217 | case 0x031350 ... 0x0323af: 218 | -------------------------------------------------------------------------------- /qrexec-lib/unicode-generator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static bool is_permitted_code_point(uint32_t const code_point) 17 | { 18 | /* 19 | * Validate that this is a Unicode codepoint that can be assigned a 20 | * character. This catches surrogates, code points beyond 0x10FFFF, and 21 | * various noncharacters. 22 | */ 23 | if (!(U_IS_UNICODE_CHAR(code_point))) 24 | return false; 25 | 26 | /* Reject all control characters */ 27 | if (code_point < 0x20) 28 | return false; 29 | 30 | /* Allow all other ASCII characters except DEL */ 31 | if (code_point < 0x7F) 32 | return true; 33 | 34 | /* 35 | * Validate that the codepoint is a valid scalar value and is not a symbol, 36 | * space, unassigned character, or control character. 37 | */ 38 | int category = u_charType(code_point); 39 | switch (category) { 40 | case U_UNASSIGNED: 41 | return false; 42 | case U_UPPERCASE_LETTER: 43 | case U_LOWERCASE_LETTER: 44 | case U_TITLECASE_LETTER: 45 | case U_MODIFIER_LETTER: 46 | case U_OTHER_LETTER: 47 | break; 48 | case U_NON_SPACING_MARK: 49 | case U_ENCLOSING_MARK: 50 | case U_COMBINING_SPACING_MARK: 51 | return false; 52 | case U_DECIMAL_DIGIT_NUMBER: 53 | case U_LETTER_NUMBER: 54 | case U_OTHER_NUMBER: 55 | break; 56 | case U_SPACE_SEPARATOR: 57 | return false; 58 | case U_LINE_SEPARATOR: 59 | case U_PARAGRAPH_SEPARATOR: 60 | case U_CONTROL_CHAR: 61 | case U_FORMAT_CHAR: 62 | case U_PRIVATE_USE_CHAR: 63 | return false; 64 | case U_DASH_PUNCTUATION: 65 | case U_START_PUNCTUATION: 66 | case U_END_PUNCTUATION: 67 | case U_CONNECTOR_PUNCTUATION: 68 | case U_OTHER_PUNCTUATION: 69 | case U_MATH_SYMBOL: 70 | case U_CURRENCY_SYMBOL: 71 | break; 72 | case U_MODIFIER_SYMBOL: 73 | case U_OTHER_SYMBOL: 74 | return false; 75 | case U_INITIAL_PUNCTUATION: 76 | case U_FINAL_PUNCTUATION: 77 | break; 78 | case U_SURROGATE: 79 | default: 80 | fprintf(stderr, "BUG: u_charType(0x%" PRIx32 ") returned unexpected value %d", code_point, category); 81 | abort(); 82 | } 83 | 84 | uint32_t s = u_charDirection(code_point); 85 | switch (s) { 86 | case U_WHITE_SPACE_NEUTRAL: 87 | case U_OTHER_NEUTRAL: 88 | case U_EUROPEAN_NUMBER_TERMINATOR: 89 | case U_EUROPEAN_NUMBER_SEPARATOR: 90 | case U_COMMON_NUMBER_SEPARATOR: 91 | case U_EUROPEAN_NUMBER: 92 | case U_LEFT_TO_RIGHT: 93 | break; 94 | default: 95 | /* Not safe */ 96 | return false; 97 | } 98 | UErrorCode errcode = 0; 99 | int script = uscript_getScript(code_point, &errcode); 100 | if (errcode) { 101 | fprintf(stderr, "BUG: uscript_getScript failed on codepoint 0x%" PRIX32 " with with code %d\n", code_point, errcode); 102 | abort(); 103 | } 104 | 105 | int v = uscript_getUsage(script) ; 106 | switch (v) { 107 | case USCRIPT_USAGE_UNKNOWN: 108 | case USCRIPT_USAGE_RECOMMENDED: 109 | break; 110 | case USCRIPT_USAGE_NOT_ENCODED: 111 | case USCRIPT_USAGE_EXCLUDED: 112 | case USCRIPT_USAGE_ASPIRATIONAL: 113 | case USCRIPT_USAGE_LIMITED_USE: 114 | return false; 115 | default: 116 | fprintf(stderr, "BUG: uscript_getUsage returned unexpected value %d codepoint 0x%" PRIX32 "\n", v, code_point); 117 | abort(); 118 | } 119 | 120 | switch (script) { 121 | case USCRIPT_INHERITED: 122 | case USCRIPT_CYRILLIC: 123 | case USCRIPT_GREEK: 124 | case USCRIPT_LATIN: 125 | case USCRIPT_BRAILLE: 126 | case USCRIPT_SIMPLIFIED_HAN: 127 | case USCRIPT_TRADITIONAL_HAN: 128 | case USCRIPT_HAN: 129 | case USCRIPT_HAN_WITH_BOPOMOFO: 130 | case USCRIPT_JAMO: 131 | case USCRIPT_HANGUL: 132 | case USCRIPT_BOPOMOFO: 133 | case USCRIPT_KATAKANA_OR_HIRAGANA: 134 | case USCRIPT_HIRAGANA: 135 | case USCRIPT_KATAKANA: 136 | case USCRIPT_JAPANESE: 137 | case USCRIPT_KOREAN: 138 | case USCRIPT_COMMON: 139 | return true; 140 | case USCRIPT_DESERET: 141 | case USCRIPT_COPTIC: 142 | case USCRIPT_LINEAR_B: 143 | case USCRIPT_ETHIOPIC: 144 | case USCRIPT_GOTHIC: 145 | case USCRIPT_OGHAM: 146 | case USCRIPT_OLD_ITALIC: 147 | case USCRIPT_UGARITIC: 148 | case USCRIPT_GLAGOLITIC: 149 | case USCRIPT_KHAROSHTHI: 150 | case USCRIPT_OLD_PERSIAN: 151 | case USCRIPT_HIERATIC_EGYPTIAN: 152 | case USCRIPT_EGYPTIAN_HIEROGLYPHS: 153 | case USCRIPT_LINEAR_A: 154 | case USCRIPT_DEMOTIC_EGYPTIAN: 155 | case USCRIPT_BRAHMI: 156 | case USCRIPT_KHUTSURI: 157 | case USCRIPT_OLD_HUNGARIAN: 158 | case USCRIPT_HARAPPAN_INDUS: 159 | case USCRIPT_MAYAN_HIEROGLYPHS: 160 | case USCRIPT_MEROITIC_HIEROGLYPHS: 161 | case USCRIPT_OLD_PERMIC: 162 | case USCRIPT_PHOENICIAN: 163 | case USCRIPT_ORKHON: 164 | case USCRIPT_RONGORONGO: 165 | case USCRIPT_CUNEIFORM: 166 | case USCRIPT_CARIAN: 167 | case USCRIPT_LYCIAN: 168 | case USCRIPT_LYDIAN: 169 | case USCRIPT_REJANG: 170 | case USCRIPT_IMPERIAL_ARAMAIC: 171 | case USCRIPT_AVESTAN: 172 | case USCRIPT_KAITHI: 173 | case USCRIPT_INSCRIPTIONAL_PAHLAVI: 174 | case USCRIPT_PSALTER_PAHLAVI: 175 | case USCRIPT_BOOK_PAHLAVI: 176 | case USCRIPT_SAMARITAN: 177 | case USCRIPT_INSCRIPTIONAL_PARTHIAN: 178 | case USCRIPT_ELBASAN: 179 | case USCRIPT_CAUCASIAN_ALBANIAN: 180 | case USCRIPT_PALMYRENE: 181 | case USCRIPT_NABATAEAN: 182 | case USCRIPT_HATRAN: 183 | case USCRIPT_MEROITIC_CURSIVE: 184 | case USCRIPT_OLD_SOUTH_ARABIAN: 185 | case USCRIPT_OLD_NORTH_ARABIAN: 186 | case USCRIPT_OLD_CHURCH_SLAVONIC_CYRILLIC: 187 | #ifdef USCRIPT_OLD_SOGDIAN 188 | case USCRIPT_OLD_SOGDIAN: 189 | #endif 190 | #ifdef USCRIPT_SOGDIAN 191 | case USCRIPT_SOGDIAN: 192 | #endif 193 | #ifdef USCRIPT_CHORASMIAN 194 | case USCRIPT_CHORASMIAN: 195 | #endif 196 | #ifdef USCRIPT_ELYMAIC 197 | case USCRIPT_ELYMAIC: 198 | #endif 199 | case USCRIPT_MAHAJANI: 200 | case USCRIPT_JURCHEN: 201 | case USCRIPT_TANGUT: 202 | case USCRIPT_WOLEAI: 203 | case USCRIPT_ANATOLIAN_HIEROGLYPHS: 204 | case USCRIPT_KHOJKI: 205 | case USCRIPT_MULTANI: 206 | case USCRIPT_MODI: 207 | case USCRIPT_AHOM: 208 | #ifdef USCRIPT_DOGRA 209 | case USCRIPT_DOGRA: 210 | #endif 211 | case USCRIPT_BHAIKSUKI: 212 | case USCRIPT_MARCHEN: 213 | case USCRIPT_ZANABAZAR_SQUARE: 214 | #ifdef USCRIPT_DIVES_AKURU 215 | case USCRIPT_DIVES_AKURU: 216 | #endif 217 | #ifdef USCRIPT_MAKASAR 218 | case USCRIPT_MAKASAR: 219 | #endif 220 | #ifdef USCRIPT_NANDINAGARI 221 | case USCRIPT_NANDINAGARI: 222 | #endif 223 | #ifdef USCRIPT_KHITAN_SMALL_SCRIPT 224 | case USCRIPT_KHITAN_SMALL_SCRIPT: 225 | #endif 226 | return false; // dead languages or scripts 227 | case USCRIPT_MENDE: 228 | case USCRIPT_MANDAIC: 229 | case USCRIPT_ARABIC: 230 | case USCRIPT_HEBREW: 231 | case USCRIPT_NKO: 232 | #ifdef USCRIPT_HANIFI_ROHINGYA 233 | case USCRIPT_HANIFI_ROHINGYA: 234 | #endif 235 | case USCRIPT_NUSHU: 236 | case USCRIPT_ADLAM: 237 | return false; // right-to-left 238 | case USCRIPT_DEVANAGARI: 239 | case USCRIPT_SYRIAC: 240 | case USCRIPT_BENGALI: 241 | case USCRIPT_BALINESE: 242 | case USCRIPT_ESTRANGELO_SYRIAC: 243 | case USCRIPT_WESTERN_SYRIAC: 244 | case USCRIPT_EASTERN_SYRIAC: 245 | case USCRIPT_GUJARATI: 246 | case USCRIPT_GURMUKHI: 247 | case USCRIPT_KANNADA: 248 | case USCRIPT_KHMER: 249 | case USCRIPT_MALAYALAM: 250 | case USCRIPT_MONGOLIAN: 251 | case USCRIPT_MYANMAR: 252 | case USCRIPT_THAI: 253 | case USCRIPT_SINHALA: 254 | case USCRIPT_TAMIL: 255 | case USCRIPT_TELUGU: 256 | case USCRIPT_THAANA: 257 | case USCRIPT_TIBETAN: 258 | case USCRIPT_ORIYA: 259 | case USCRIPT_PHAGS_PA: 260 | case USCRIPT_LIMBU: 261 | case USCRIPT_LAO: 262 | case USCRIPT_TAGALOG: 263 | case USCRIPT_BUHID: 264 | case USCRIPT_TAI_LE: 265 | case USCRIPT_BUGINESE: 266 | case USCRIPT_BATAK: 267 | case USCRIPT_CHAM: 268 | case USCRIPT_JAVANESE: 269 | case USCRIPT_LEPCHA: 270 | case USCRIPT_MIAO: 271 | case USCRIPT_LANNA: 272 | case USCRIPT_SAURASHTRA: 273 | case USCRIPT_CHAKMA: 274 | case USCRIPT_TAI_VIET: 275 | case USCRIPT_KHUDAWADI: 276 | case USCRIPT_TAKRI: 277 | case USCRIPT_NEWA: 278 | case USCRIPT_SOYOMBO: 279 | case USCRIPT_SIGN_WRITING: 280 | return false; // require complex rendering 281 | default: 282 | return false; // not sure 283 | } 284 | } 285 | 286 | // Generate the table of allowed codepoints, 287 | // as a bunch of cases in a switch statement. 288 | static void print_code_point_list(FILE *out) 289 | { 290 | bool last_allowed = false; 291 | uint32_t range_start = 0; 292 | for (uint32_t v = 0x20; v < 0x110000; ++v) { 293 | bool this_allowed = is_permitted_code_point(v); 294 | if (v < 0x7F) 295 | assert(this_allowed); 296 | if (this_allowed ^ last_allowed) { 297 | last_allowed = this_allowed; 298 | if (this_allowed) { 299 | range_start = v; 300 | // Start a new list 301 | if (fprintf(out, " case 0x%06" PRIx32, v) != 17) 302 | err(1, "fprintf()"); 303 | } else { 304 | if (v - range_start > 1) { 305 | if (fprintf(out, " ... 0x%06" PRIx32 ":\n", v - 1) != 15) 306 | err(1, "fprintf()"); 307 | } else { 308 | if (fwrite(":\n", 1, 2, out) != 2) 309 | err(1, "fprintf()"); 310 | } 311 | } 312 | } 313 | } 314 | if (last_allowed) 315 | errx(1, "BUG: should not allow 0x10FFFF"); 316 | if (fflush(out)) 317 | err(1, "fflush()"); 318 | switch (fsync(fileno(out))) { 319 | case 0: 320 | return; 321 | case -1: 322 | if ((errno != EROFS) && (errno != EINVAL)) 323 | err(1, "fsync()"); 324 | return; 325 | default: 326 | abort(); 327 | } 328 | } 329 | 330 | int main(int argc, char **argv) 331 | { 332 | if (argc != 1) 333 | errx(1, "No arguments expected"); 334 | (void)argv; 335 | print_code_point_list(stdout); 336 | } 337 | -------------------------------------------------------------------------------- /qrexec-lib/unicode.c: -------------------------------------------------------------------------------- 1 | #define U_HIDE_DEPRECATED_API U_HIDE_DEPRECATED_API 2 | 3 | #include "pure.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | QUBES_PURE_PUBLIC bool 12 | qubes_pure_code_point_safe_for_display(uint32_t code_point) { 13 | switch (code_point) { 14 | #include "unicode-allowlist-table.c" 15 | return true; 16 | default: 17 | return false; 18 | } 19 | } 20 | 21 | /* validate single UTF-8 character 22 | * return bytes count of this character, or 0 if the character is invalid */ 23 | static int validate_utf8_char(const uint8_t *untrusted_c) { 24 | int tails_count = 0; 25 | int total_size = 0; 26 | uint32_t code_point; 27 | /* it is safe to access byte pointed by the parameter, 28 | * but every next byte can access only if previous byte was not NUL. 29 | */ 30 | 31 | /* According to http://www.ietf.org/rfc/rfc3629.txt: 32 | * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 33 | * UTF8-1 = %x00-7F 34 | * UTF8-2 = %xC2-DF UTF8-tail 35 | * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / 36 | * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) 37 | * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / 38 | * %xF4 %x80-8F 2( UTF8-tail ) 39 | * UTF8-tail = %x80-BF 40 | * 41 | * This code uses a slightly different grammar: 42 | * 43 | * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 44 | * UTF8-1 = %x20-7F 45 | * UTF8-2 = %xC2-DF UTF8-tail 46 | * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EF 2( UTF8-tail ) 47 | * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F4 3( UTF8-tail ) 48 | * UTF8-tail = %x80-BF 49 | * 50 | * The differences are: 51 | * 52 | * - ASCII control characters are rejected, allowing a fast path for other 53 | * ASCII characters. 54 | * - Surrogates and some values above 0x10FFFF are accepted here, but are 55 | * rejected as forbidden code points later. 56 | */ 57 | switch (*untrusted_c) { 58 | case 0xC2 ... 0xDF: 59 | total_size = 2; 60 | tails_count = 1; 61 | code_point = *untrusted_c & 0x1F; 62 | break; 63 | case 0xE0: 64 | untrusted_c++; 65 | total_size = 3; 66 | if (*untrusted_c >= 0xA0 && *untrusted_c <= 0xBF) 67 | tails_count = 1; 68 | else 69 | return 0; 70 | code_point = *untrusted_c & 0x3F; 71 | break; 72 | case 0xE1 ... 0xEF: 73 | total_size = 3; 74 | tails_count = 2; 75 | code_point = *untrusted_c & 0xF; 76 | break; 77 | case 0xF0: 78 | untrusted_c++; 79 | total_size = 4; 80 | if (*untrusted_c >= 0x90 && *untrusted_c <= 0xBF) 81 | tails_count = 2; 82 | else 83 | return 0; 84 | code_point = *untrusted_c & 0x3F; 85 | break; 86 | case 0xF1 ... 0xF4: 87 | total_size = 4; 88 | tails_count = 3; 89 | code_point = *untrusted_c & 0x7; 90 | break; 91 | default: 92 | return 0; // control ASCII or invalid UTF-8 93 | } 94 | 95 | while (tails_count-- > 0) { 96 | untrusted_c++; 97 | if (!(*untrusted_c >= 0x80 && *untrusted_c <= 0xBF)) 98 | return 0; 99 | code_point = code_point << 6 | (*untrusted_c & 0x3F); 100 | } 101 | 102 | return qubes_pure_code_point_safe_for_display(code_point) ? total_size : 0; 103 | } 104 | 105 | // Statically assert that a statement is not reachable. 106 | // 107 | // At runtime, this is just abort(), but it comes with a neat trick: 108 | // if optimizations are on, CHECK_UNREACHABLE is defined, and the compiler 109 | // claims to be GNU-compatible, the compiler must prove that this is 110 | // unreachable. Otherwise, it is a compile-time error. 111 | // 112 | // To enable static checking of this macro, pass CHECK_UNREACHABLE=1 to the 113 | // makefile or include it in the environment. 114 | #if defined __GNUC__ && defined __OPTIMIZE__ && defined CHECK_UNREACHABLE 115 | #define COMPILETIME_UNREACHABLE do { \ 116 | extern void not_reachable(void) \ 117 | __attribute__(( \ 118 | error("Compiler could not prove that this statement is not reachable"), \ 119 | noreturn)); \ 120 | not_reachable(); \ 121 | } while (0) 122 | #else 123 | #define COMPILETIME_UNREACHABLE do { \ 124 | assert(0); \ 125 | abort(); \ 126 | } while (0) 127 | #endif 128 | 129 | // This is one of the trickiest, most security-critical functions in the 130 | // whole repository (opendir_safe() in unpack.c is the other). It is critical 131 | // for preventing directory traversal attacks. The code does use a chroot() 132 | // and a bind mount, but the bind mount is not always effective if mount 133 | // namespaces are in use, and the chroot can be bypassed (QSB-015). 134 | // 135 | // Preconditions: 136 | // 137 | // - untrusted_name is NUL-terminated. 138 | // - allowed_leading_dotdot is the maximum number of leading "../" sequences 139 | // allowed. Might be 0. 140 | // 141 | // Algorithm: 142 | // 143 | // At the start of the loop and after '/', the code checks for '/' and '.'. 144 | // '/', "./", or ".\0" indicate a non-canonical path. These are currently 145 | // rejected, but they could safely be accepted in the future without allowing 146 | // directory traversal attacks. "../" and "..\0" are ".." components: the code 147 | // checks that the limit on non-".." components has not been exceeded, fails if 148 | // it has, and otherwise decrements the limit. This ensures that a directory 149 | // tree cannot contain symlinks that point outside of the tree itself. 150 | // Anything else is a normal path component: the limit on ".." components 151 | // is set to zero, and the number of non-".." components is incremented. 152 | // 153 | // The return value is the number of non-".." components on 154 | // success, or a negative errno value on failure. The return value might be 155 | // zero. 156 | static ssize_t validate_path(const uint8_t *const untrusted_name, 157 | size_t allowed_leading_dotdot, 158 | const uint32_t flags) 159 | { 160 | // We assume that there are not SSIZE_MAX path components. 161 | // This cannot happen on hardware using a flat address space, 162 | // as this would require SIZE_MAX bytes in the path and leave 163 | // no space for the executable code. 164 | ssize_t non_dotdot_components = 0; 165 | bool const allow_non_canonical = (flags & QUBES_PURE_ALLOW_NON_CANONICAL_PATHS); 166 | if (untrusted_name[0] == '\0') 167 | return allow_non_canonical ? 0 : -ENOLINK; // empty path 168 | if (untrusted_name[0] == '/') 169 | return -ENOLINK; // absolute path 170 | size_t i; 171 | for (i = 0; untrusted_name[i]; i++) { 172 | if (i == 0 || untrusted_name[i - 1] == '/') { 173 | // Start of a path component 174 | switch (untrusted_name[i]) { 175 | case '\0': // impossible, loop exit condition & if statement before 176 | // loop check this 177 | COMPILETIME_UNREACHABLE; 178 | case '/': // repeated slash 179 | if (allow_non_canonical) 180 | continue; 181 | return -EILSEQ; 182 | case '.': 183 | if (untrusted_name[i + 1] == '\0' || untrusted_name[i + 1] == '/') { 184 | // Path component is "." 185 | if (allow_non_canonical) 186 | continue; 187 | return -EILSEQ; 188 | } 189 | if ((untrusted_name[i + 1] == '.') && 190 | (untrusted_name[i + 2] == '\0' || untrusted_name[i + 2] == '/')) { 191 | /* Check if the limit on leading ".." components has been exceeded */ 192 | if (allowed_leading_dotdot < 1) 193 | return -ENOLINK; 194 | allowed_leading_dotdot--; 195 | i++; // loop will advance past second "." 196 | continue; 197 | } 198 | __attribute__((fallthrough)); 199 | default: 200 | allowed_leading_dotdot = 0; // do not allow further ".." components 201 | non_dotdot_components++; 202 | break; 203 | } 204 | } 205 | if (untrusted_name[i] == 0) { 206 | // If this is violated, the subsequent i++ will be out of bounds 207 | COMPILETIME_UNREACHABLE; 208 | } else if ((0x20 <= untrusted_name[i] && untrusted_name[i] <= 0x7E) || 209 | (flags & QUBES_PURE_ALLOW_UNSAFE_CHARACTERS) != 0) { 210 | /* loop will advance past this */ 211 | } else { 212 | int utf8_ret = validate_utf8_char((const unsigned char *)(untrusted_name + i)); 213 | if (utf8_ret > 0) { 214 | i += (size_t)(utf8_ret - 1); /* loop will do one more increment */ 215 | } else { 216 | return -EILSEQ; 217 | } 218 | } 219 | } 220 | if (i < 1 || untrusted_name[i]) { 221 | // ideally this would be COMPILETIME_UNREACHABLE but GCC can't prove this 222 | assert(0); 223 | return -EILSEQ; 224 | } 225 | if ((flags & QUBES_PURE_ALLOW_TRAILING_SLASH) == 0 && 226 | untrusted_name[i - 1] == '/') 227 | return -EILSEQ; 228 | return non_dotdot_components; 229 | } 230 | 231 | static bool flag_check(const uint32_t flags) 232 | { 233 | int const allowed = (QUBES_PURE_ALLOW_UNSAFE_CHARACTERS | 234 | QUBES_PURE_ALLOW_NON_CANONICAL_SYMLINKS | 235 | QUBES_PURE_ALLOW_NON_CANONICAL_PATHS | 236 | QUBES_PURE_ALLOW_TRAILING_SLASH | 237 | QUBES_PURE_ALLOW_UNSAFE_SYMLINKS); 238 | return (flags & ~(__typeof__(flags))allowed) == 0; 239 | } 240 | 241 | QUBES_PURE_PUBLIC int 242 | qubes_pure_validate_file_name_v2(const uint8_t *const untrusted_filename, 243 | const uint32_t flags) 244 | { 245 | if (!flag_check(flags)) 246 | return -EINVAL; 247 | // We require at least one non-".." component in the path. 248 | ssize_t res = validate_path(untrusted_filename, 0, flags); 249 | // Always return -EILSEQ, since -ENOLINK only makes sense for symlinks 250 | return res > 0 ? 0 : -EILSEQ; 251 | } 252 | 253 | QUBES_PURE_PUBLIC bool 254 | qubes_pure_validate_file_name(const uint8_t *const untrusted_filename) 255 | { 256 | return qubes_pure_validate_file_name_v2(untrusted_filename, 257 | QUBES_PURE_ALLOW_TRAILING_SLASH) == 0; 258 | } 259 | 260 | QUBES_PURE_PUBLIC int 261 | qubes_pure_validate_symbolic_link_v2(const uint8_t *untrusted_name, 262 | const uint8_t *untrusted_target, 263 | uint32_t flags) 264 | { 265 | if (!flag_check(flags)) 266 | return -EINVAL; 267 | ssize_t depth = validate_path(untrusted_name, 0, flags); 268 | if (depth < 0) 269 | return -EILSEQ; // -ENOLINK is only for symlinks 270 | if ((flags & QUBES_PURE_ALLOW_UNSAFE_SYMLINKS) != 0) 271 | return depth > 0 ? 0 : -ENOLINK; 272 | if ((flags & QUBES_PURE_ALLOW_NON_CANONICAL_SYMLINKS) != 0) 273 | flags |= QUBES_PURE_ALLOW_NON_CANONICAL_PATHS; 274 | // Symlink paths must have at least 2 components: "a/b" is okay 275 | // but "a" is not. This ensures that the toplevel "a" entry 276 | // is not a symbolic link. 277 | if (depth < 2) 278 | return -ENOLINK; 279 | // Symlinks must have at least 2 more path components in the name 280 | // than the number of leading ".." path elements in the target. 281 | // "a/b" can point to "c" (which resolves to "a/c") but not "../c" 282 | // (which resolves to "c"). Similarly and "a/b/c" can point to "../d" 283 | // (which resolves to "a/d") but not "../../d" (which resolves to "d"). 284 | // This ensures that ~/QubesIncoming/QUBENAME/a/b cannot point outside 285 | // of ~/QubesIncoming/QUBENAME/a. Always allow trailing slash in the 286 | // symbolic link target, whether or not they are allowed in the path. 287 | ssize_t res = validate_path(untrusted_target, (size_t)(depth - 2), 288 | flags | QUBES_PURE_ALLOW_TRAILING_SLASH); 289 | return res < 0 ? res : 0; 290 | } 291 | 292 | QUBES_PURE_PUBLIC bool 293 | qubes_pure_validate_symbolic_link(const uint8_t *untrusted_name, 294 | const uint8_t *untrusted_target) 295 | { 296 | return qubes_pure_validate_symbolic_link_v2(untrusted_name, untrusted_target, 297 | QUBES_PURE_ALLOW_TRAILING_SLASH) == 0; 298 | } 299 | 300 | QUBES_PURE_PUBLIC bool 301 | qubes_pure_string_safe_for_display(const char *untrusted_str, size_t line_length) 302 | { 303 | assert(line_length == 0 && "Not yet implemented: nonzero line length"); 304 | size_t i = 0; 305 | do { 306 | if (untrusted_str[i] >= 0x20 && untrusted_str[i] <= 0x7E) { 307 | i++; 308 | } else { 309 | int utf8_ret = validate_utf8_char((const uint8_t *)(untrusted_str + i)); 310 | if (utf8_ret > 0) { 311 | i += utf8_ret; 312 | } else { 313 | return false; 314 | } 315 | } 316 | } while (untrusted_str[i]); 317 | return true; 318 | } 319 | -------------------------------------------------------------------------------- /qrexec-lib/unpack.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE /* For O_NOFOLLOW. */ 2 | #define U_HIDE_DEPRECATED_API U_HIDE_DEPRECATED_API 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "libqubes-rpc-filecopy.h" 18 | #include "pure.h" 19 | #include "ioall.h" 20 | #include "crc32.h" 21 | 22 | static char untrusted_namebuf[MAX_PATH_LENGTH]; 23 | static unsigned long long bytes_limit = 0; 24 | static unsigned long long files_limit = 0; 25 | static unsigned long long total_bytes = 0; 26 | static unsigned long long total_files = 0; 27 | static int verbose = 0; 28 | /* 29 | * If positive, wait for disk space before extracting a file, 30 | * keeping this much extra space (in bytes). 31 | */ 32 | static unsigned long opt_wait_for_space_margin; 33 | static int use_tmpfile = 0; 34 | static int procdir_fd = -1; 35 | 36 | void send_status_and_crc(int code, const char *last_filename); 37 | 38 | /* copy from asm-generic/fcntl.h */ 39 | #ifndef __O_TMPFILE 40 | #define __O_TMPFILE 020000000 41 | #endif 42 | #ifndef O_TMPFILE 43 | /* a horrid kludge trying to make sure that this will fail on old kernels */ 44 | #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) 45 | #define O_TMPFILE_MASK (__O_TMPFILE | O_DIRECTORY | O_CREAT) 46 | #endif 47 | 48 | static _Noreturn void do_exit(int code, const char *last_filename) 49 | { 50 | close(0); 51 | send_status_and_crc(code, last_filename); 52 | exit(code); 53 | } 54 | 55 | void set_size_limit(unsigned long long new_bytes_limit, unsigned long long new_files_limit) 56 | { 57 | bytes_limit = new_bytes_limit; 58 | files_limit = new_files_limit; 59 | } 60 | 61 | void set_verbose(int value) 62 | { 63 | verbose = value; 64 | } 65 | 66 | void set_wait_for_space(unsigned long value) 67 | { 68 | opt_wait_for_space_margin = value; 69 | } 70 | 71 | void set_procfs_fd(int value) 72 | { 73 | procdir_fd = value; 74 | use_tmpfile = 1; 75 | } 76 | 77 | static int wait_for_space(int fd, unsigned long how_much) { 78 | int counter = 0; 79 | struct statvfs fs_space; 80 | do { 81 | if (fstatvfs(fd, &fs_space) == -1) { 82 | perror("fstatvfs"); 83 | return -1; 84 | } 85 | // TODO: timeout? 86 | if (counter > 0) 87 | usleep(1000000); 88 | counter++; 89 | } while (fs_space.f_bsize * fs_space.f_bavail < how_much); 90 | return 0; 91 | } 92 | 93 | static unsigned long crc32_sum = 0; 94 | static int read_all_with_crc(int fd, void *buf, int size) { 95 | int ret; 96 | ret = read_all(fd, buf, size); 97 | if (ret) 98 | crc32_sum = Crc32_ComputeBuf(crc32_sum, buf, size); 99 | return ret; 100 | } 101 | 102 | void send_status_and_crc(int code, const char *last_filename) { 103 | struct result_header hdr; 104 | struct result_header_ext hdr_ext; 105 | int saved_errno; 106 | 107 | saved_errno = errno; 108 | hdr.error_code = code; 109 | hdr._pad = 0; 110 | hdr.crc32 = crc32_sum; 111 | if (!write_all(1, &hdr, sizeof(hdr))) 112 | perror("write status"); 113 | if (last_filename) { 114 | hdr_ext.last_namelen = strlen(last_filename); 115 | if (!write_all(1, &hdr_ext, sizeof(hdr_ext))) 116 | perror("write status ext"); 117 | if (!write_all(1, last_filename, hdr_ext.last_namelen)) 118 | perror("write last_filename"); 119 | } 120 | errno = saved_errno; 121 | } 122 | 123 | static long validate_utime_nsec(uint32_t untrusted_nsec) 124 | { 125 | enum { MAX_NSEC = 999999999L }; 126 | if (untrusted_nsec > MAX_NSEC) 127 | errx(1, "Invalid nanoseconds value %" PRIu32, untrusted_nsec); 128 | return (long)untrusted_nsec; 129 | } 130 | 131 | static void fix_times_and_perms(const int fd, 132 | const struct file_header *const untrusted_hdr, 133 | const char *const untrusted_name) 134 | { 135 | const struct timespec times[2] = 136 | { 137 | { 138 | .tv_sec = untrusted_hdr->atime, 139 | .tv_nsec = validate_utime_nsec(untrusted_hdr->atime_nsec) 140 | }, 141 | { 142 | .tv_sec = untrusted_hdr->mtime, 143 | .tv_nsec = validate_utime_nsec(untrusted_hdr->mtime_nsec) 144 | }, 145 | }; 146 | /* Do not change the mode of symbolic links */ 147 | if (!S_ISLNK(untrusted_hdr->mode) && 148 | fchmod(fd, untrusted_hdr->mode & 07777)) 149 | do_exit(errno, untrusted_name); 150 | if (futimens(fd, times)) /* as above */ 151 | do_exit(errno, untrusted_name); 152 | } 153 | 154 | // Open the second-to-last component of a path, enforcing O_NOFOLLOW for every 155 | // path component. *last_segment will be set to the last segment of the path, 156 | // and points into the original path. The original path is modified in-place, 157 | // so one should probably pass a copy. The return value is either dirfd (if the 158 | // path has no / in it) or a newly opened file descriptor that must be closed by 159 | // the caller. dirfd can be AT_FDCWD to indicate the current_directory. 160 | static int opendir_safe(int dirfd, char *path, const char **last_segment) 161 | { 162 | assert(path && *path); // empty paths rejected earlier 163 | char *this_segment = path, *next_segment = NULL; 164 | *last_segment = NULL; 165 | int cur_fd = dirfd; 166 | for (;;this_segment = next_segment) { 167 | assert(this_segment); 168 | char *next = strchr(this_segment, '/'); 169 | if (next == NULL) { 170 | *last_segment = this_segment; 171 | return cur_fd; 172 | } 173 | *next = '\0'; 174 | next_segment = next + 1; 175 | if ((next - this_segment <= 2) && 176 | (memcmp(this_segment, "..", (size_t)(next - this_segment)) == 0)) { 177 | fprintf(stderr, "BUG: path component '%s' not rejected earlier!\n", this_segment); 178 | abort(); 179 | } 180 | int new_fd = openat(cur_fd, this_segment, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_NOCTTY | O_CLOEXEC); 181 | if (new_fd == -1) 182 | do_exit(errno, this_segment); 183 | if (cur_fd != dirfd) 184 | close(cur_fd); 185 | cur_fd = new_fd; 186 | } 187 | } 188 | 189 | static void process_one_file_reg(struct file_header *untrusted_hdr, 190 | const char *untrusted_name, 191 | uint32_t flags) 192 | { 193 | int ret; 194 | int fdout = -1, safe_dirfd; 195 | const char *last_segment; 196 | char *path_dup; 197 | 198 | ret = qubes_pure_validate_file_name_v2((const uint8_t *)untrusted_name, flags); 199 | if (ret != 0) 200 | do_exit(-ret, untrusted_name); /* FIXME: better error message */ 201 | if ((path_dup = strdup(untrusted_name)) == NULL) 202 | do_exit(ENOMEM, untrusted_name); 203 | safe_dirfd = opendir_safe(AT_FDCWD, path_dup, &last_segment); 204 | 205 | /* make the file inaccessible until fully written */ 206 | if (use_tmpfile) { 207 | fdout = openat(safe_dirfd, ".", O_WRONLY | O_TMPFILE | O_CLOEXEC | O_NOCTTY, 0700); 208 | if (fdout < 0) { 209 | if (errno==ENOENT || /* most likely, kernel too old for O_TMPFILE */ 210 | errno==EOPNOTSUPP) /* filesystem has no support for O_TMPFILE */ 211 | use_tmpfile = 0; 212 | else 213 | do_exit(errno, untrusted_name); 214 | } 215 | } 216 | 217 | if (fdout < 0) 218 | fdout = openat(safe_dirfd, last_segment, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, 0000); 219 | if (fdout < 0) 220 | do_exit(errno, untrusted_name); 221 | 222 | /* sizes are signed elsewhere */ 223 | if (untrusted_hdr->filelen > LLONG_MAX || (bytes_limit && untrusted_hdr->filelen > bytes_limit)) 224 | do_exit(EDQUOT, untrusted_name); 225 | if (bytes_limit && total_bytes > bytes_limit - untrusted_hdr->filelen) 226 | do_exit(EDQUOT, untrusted_name); 227 | if (opt_wait_for_space_margin) { 228 | wait_for_space(fdout, 229 | untrusted_hdr->filelen + opt_wait_for_space_margin); 230 | } 231 | total_bytes += untrusted_hdr->filelen; 232 | ret = copy_file(fdout, 0, untrusted_hdr->filelen, &crc32_sum); 233 | if (ret != COPY_FILE_OK) { 234 | if (ret == COPY_FILE_READ_EOF 235 | || ret == COPY_FILE_READ_ERROR) 236 | do_exit(LEGAL_EOF, untrusted_name); // hopefully remote will produce error message 237 | else 238 | do_exit(errno, untrusted_name); 239 | } 240 | if (use_tmpfile) { 241 | char fd_str[11]; 242 | if ((unsigned)snprintf(fd_str, sizeof(fd_str), "%d", fdout) >= sizeof(fd_str)) 243 | abort(); 244 | if (linkat(procdir_fd, fd_str, safe_dirfd, last_segment, AT_SYMLINK_FOLLOW) < 0) 245 | do_exit(errno, untrusted_name); 246 | } 247 | fix_times_and_perms(fdout, untrusted_hdr, untrusted_name); 248 | if (safe_dirfd != AT_FDCWD) 249 | close(safe_dirfd); 250 | close(fdout); 251 | free(path_dup); 252 | } 253 | 254 | 255 | static void process_one_file_dir(struct file_header *untrusted_hdr, 256 | const char *untrusted_name, 257 | uint32_t flags) 258 | { 259 | int safe_dirfd; 260 | const char *last_segment; 261 | char *path_dup; 262 | int rc = qubes_pure_validate_file_name_v2((const uint8_t *)untrusted_name, flags); 263 | if (rc != 0) 264 | do_exit(rc, untrusted_name); /* FIXME: better error message */ 265 | if ((path_dup = strdup(untrusted_name)) == NULL) 266 | do_exit(ENOMEM, untrusted_name); 267 | safe_dirfd = opendir_safe(AT_FDCWD, path_dup, &last_segment); 268 | 269 | // fix perms only when the directory is sent for the second time 270 | // it allows to transfer r.x directory contents, as we create it rwx initially 271 | struct stat buf; 272 | if (!mkdirat(safe_dirfd, last_segment, 0700)) { 273 | close(safe_dirfd); 274 | return; 275 | } 276 | if (errno != EEXIST) 277 | do_exit(errno, untrusted_name); 278 | int new_dirfd = openat(safe_dirfd, last_segment, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_DIRECTORY); 279 | if (new_dirfd < 0 || fstat(new_dirfd, &buf) < 0) 280 | do_exit(errno, untrusted_name); 281 | total_bytes += buf.st_size; 282 | /* size accumulated after the fact, so don't check limit here */ 283 | fix_times_and_perms(new_dirfd, untrusted_hdr, untrusted_name); 284 | close(new_dirfd); 285 | if (safe_dirfd != AT_FDCWD) 286 | close(safe_dirfd); 287 | free(path_dup); 288 | } 289 | 290 | static void process_one_file_link(struct file_header *untrusted_hdr, 291 | const char *untrusted_name, 292 | uint32_t flags) 293 | { 294 | char untrusted_content[MAX_PATH_LENGTH]; 295 | const char *last_segment; 296 | char *path_dup; 297 | unsigned int filelen; 298 | int safe_dirfd; 299 | if (untrusted_hdr->filelen > MAX_PATH_LENGTH - 1) 300 | do_exit(ENAMETOOLONG, untrusted_name); 301 | 302 | filelen = untrusted_hdr->filelen; /* sanitized above */ 303 | total_bytes += filelen; 304 | if (bytes_limit && total_bytes > bytes_limit) 305 | do_exit(EDQUOT, untrusted_name); 306 | if (!read_all_with_crc(0, untrusted_content, filelen)) 307 | do_exit(LEGAL_EOF, untrusted_name); // hopefully remote has produced error message 308 | untrusted_content[filelen] = 0; 309 | /* 310 | * Sanitize both the path of the symbolic link and its target. 311 | * Ensure that no immediate subdirectory of ~/QubesIncoming/VMNAME 312 | * may have symlinks that point out of it. 313 | */ 314 | int rc = qubes_pure_validate_symbolic_link_v2((const uint8_t *)untrusted_name, 315 | (const uint8_t *)untrusted_content, 316 | flags); 317 | if (rc != 0) 318 | do_exit(-rc, untrusted_content); 319 | 320 | if ((path_dup = strdup(untrusted_name)) == NULL) 321 | do_exit(ENOMEM, untrusted_name); 322 | safe_dirfd = opendir_safe(AT_FDCWD, path_dup, &last_segment); 323 | 324 | if (symlinkat(untrusted_content, safe_dirfd, last_segment)) 325 | do_exit(errno, untrusted_name); 326 | 327 | if (safe_dirfd != AT_FDCWD) 328 | close(safe_dirfd); 329 | free(path_dup); 330 | } 331 | 332 | static void process_one_file(struct file_header *untrusted_hdr, int flags) 333 | { 334 | unsigned int namelen; 335 | if (untrusted_hdr->namelen > MAX_PATH_LENGTH - 1) 336 | do_exit(ENAMETOOLONG, NULL); /* filename too long so not received at all */ 337 | namelen = untrusted_hdr->namelen; /* sanitized above */ 338 | // Never set QUBES_PURE_ALLOW_NON_CANONICAL_PATHS -- paths from qfile-agent 339 | // will always be canonical. 340 | uint32_t validate_flags = ((uint32_t)flags >> 2) & 341 | (QUBES_PURE_ALLOW_UNSAFE_CHARACTERS | QUBES_PURE_ALLOW_UNSAFE_SYMLINKS | 342 | QUBES_PURE_ALLOW_NON_CANONICAL_SYMLINKS); 343 | if (!read_all_with_crc(0, untrusted_namebuf, namelen)) 344 | do_exit(LEGAL_EOF, NULL); // hopefully remote has produced error message 345 | untrusted_namebuf[namelen] = 0; 346 | if (S_ISREG(untrusted_hdr->mode)) 347 | process_one_file_reg(untrusted_hdr, untrusted_namebuf, validate_flags); 348 | else if (S_ISLNK(untrusted_hdr->mode) && (flags & COPY_ALLOW_SYMLINKS)) 349 | process_one_file_link(untrusted_hdr, untrusted_namebuf, validate_flags); 350 | else if (S_ISDIR(untrusted_hdr->mode) && (flags & COPY_ALLOW_DIRECTORIES)) 351 | process_one_file_dir(untrusted_hdr, untrusted_namebuf, validate_flags); 352 | else 353 | do_exit(EINVAL, untrusted_namebuf); 354 | if (verbose && !S_ISDIR(untrusted_hdr->mode)) 355 | fprintf(stderr, "%s\n", untrusted_namebuf); 356 | } 357 | 358 | int do_unpack(void) { 359 | return do_unpack_ext(COPY_ALLOW_DIRECTORIES | COPY_ALLOW_SYMLINKS); 360 | } 361 | 362 | int do_unpack_ext(int flags) 363 | { 364 | struct file_header untrusted_hdr; 365 | int end_of_transfer_marker_seen = 0; 366 | int cwd_fd; 367 | int saved_errno; 368 | 369 | total_bytes = total_files = 0; 370 | /* initialize checksum */ 371 | crc32_sum = 0; 372 | while (read_all_with_crc(0, &untrusted_hdr, sizeof untrusted_hdr)) { 373 | if (untrusted_hdr.namelen == 0) { 374 | end_of_transfer_marker_seen = 1; 375 | errno = 0; 376 | break; 377 | } 378 | total_files++; 379 | if (files_limit && total_files > files_limit) 380 | do_exit(EDQUOT, untrusted_namebuf); 381 | process_one_file(&untrusted_hdr, flags); 382 | } 383 | if (!end_of_transfer_marker_seen && !errno) 384 | errno = EREMOTEIO; 385 | 386 | saved_errno = errno; 387 | cwd_fd = open(".", O_RDONLY); 388 | if (cwd_fd >= 0 && syncfs(cwd_fd) == 0 && close(cwd_fd) == 0) 389 | errno = saved_errno; 390 | 391 | send_status_and_crc(errno, untrusted_namebuf); 392 | return errno; 393 | } 394 | -------------------------------------------------------------------------------- /qrexec-lib/validator-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "pure.h" 7 | #include 8 | #ifdef NDEBUG 9 | // without assertions this test program would not test anything 10 | # error "String validation test program does not work without assertions." 11 | #endif 12 | #include 13 | 14 | static void character_must_be_allowed(UChar32 c) 15 | { 16 | char buf[5]; 17 | int32_t off = 0; 18 | UBool e = false; 19 | U8_APPEND((uint8_t *)buf, off, 4, c, e); 20 | assert(!e && off <= 4); 21 | buf[off] = 0; 22 | if (!qubes_pure_code_point_safe_for_display(c) || 23 | !qubes_pure_string_safe_for_display(buf, 0)) 24 | { 25 | fprintf(stderr, "BUG: cannot handle file name %s (codepoint U+%" PRIx32 ")\n", buf, (int32_t)c); 26 | abort(); 27 | } 28 | } 29 | 30 | static void character_must_be_forbidden(UChar32 c) 31 | { 32 | uint8_t buf[128]; 33 | int32_t off = 0; 34 | if (qubes_pure_code_point_safe_for_display(c)) { 35 | fprintf(stderr, "BUG: allowed codepoint U+%" PRIx32 "\n", (int32_t)c); 36 | abort(); 37 | } else if (c < 0) { 38 | return; // cannot be encoded sensibly 39 | } else if (c < (1 << 7)) { 40 | buf[off++] = c; 41 | } else if (c < (1 << 11)) { 42 | buf[off++] = (0xC0 | (c >> 6)); 43 | buf[off++] = (0x80 | (c & 0x3F)); 44 | } else if (c < (1L << 16)) { 45 | buf[off++] = (0xE0 | (c >> 12)); 46 | buf[off++] = (0x80 | ((c >> 6) & 0x3F)); 47 | buf[off++] = (0x80 | (c & 0x3F)); 48 | } else if (c < 0x140000) { 49 | buf[off++] = (0xF0 | (c >> 18)); 50 | buf[off++] = (0x80 | ((c >> 12) & 0x3F)); 51 | buf[off++] = (0x80 | ((c >> 6) & 0x3F)); 52 | buf[off++] = (0x80 | (c & 0x3F)); 53 | } else { 54 | return; // trivially rejected 55 | } 56 | if (c < 0x110000 && !U_IS_SURROGATE(c)) { 57 | UChar32 compare_c; 58 | U8_GET(buf, 0, 0, off, compare_c); 59 | assert(compare_c >= 0); 60 | assert(compare_c == c); 61 | } 62 | 63 | buf[off++] = 0; 64 | if (qubes_pure_string_safe_for_display((const char *)buf, 0)) 65 | { 66 | fprintf(stderr, "BUG: allowed string with codepoint U+%" PRIx32 "\n", (int32_t)c); 67 | abort(); 68 | } 69 | } 70 | struct symlink_test { 71 | const char *const path, *const target, *const file; 72 | int const line, flags; 73 | bool const allowed; 74 | }; 75 | 76 | // returns 0 on success and nonzero on failure 77 | static int symlink_test(const struct symlink_test symlink_checks[], size_t size) 78 | { 79 | bool failed = false; 80 | for (size_t i = 0; i < size; ++i) { 81 | const struct symlink_test *p = symlink_checks + i; 82 | if ((qubes_pure_validate_symbolic_link_v2((const unsigned char *)p->path, 83 | (const unsigned char *)p->target, 84 | p->flags) == 0) != p->allowed) { 85 | failed = true; 86 | fprintf(stderr, "%s:%d:Test failure\n", p->file, p->line); 87 | } 88 | } 89 | return (int)failed; 90 | } 91 | 92 | int main(int argc, char **argv) 93 | { 94 | (void)argc; 95 | (void)argv; 96 | assert(qubes_pure_validate_file_name((const uint8_t *)u8"simple_safe_filename.txt")); 97 | 98 | // Directory traversal checks 99 | assert(!qubes_pure_validate_file_name((const uint8_t *)"..")); 100 | assert(!qubes_pure_validate_file_name((const uint8_t *)"../..")); 101 | assert(!qubes_pure_validate_file_name((const uint8_t *)"a/..")); 102 | assert(!qubes_pure_validate_file_name((const uint8_t *)"a/../b")); 103 | assert(!qubes_pure_validate_file_name((const uint8_t *)"/")); 104 | assert(!qubes_pure_validate_file_name((const uint8_t *)"//")); 105 | assert(!qubes_pure_validate_file_name((const uint8_t *)"///")); 106 | assert(!qubes_pure_validate_file_name((const uint8_t *)"/a")); 107 | assert(!qubes_pure_validate_file_name((const uint8_t *)"//a")); 108 | assert(!qubes_pure_validate_file_name((const uint8_t *)"///a")); 109 | 110 | // No repeated slashes 111 | assert(!qubes_pure_validate_file_name((const uint8_t *)"a//b")); 112 | 113 | // No "." as a path component 114 | assert(!qubes_pure_validate_file_name((const uint8_t *)".")); 115 | assert(!qubes_pure_validate_file_name((const uint8_t *)"a/.")); 116 | assert(!qubes_pure_validate_file_name((const uint8_t *)"./a")); 117 | assert(!qubes_pure_validate_file_name((const uint8_t *)"a/./a")); 118 | 119 | // No ".." as a path component 120 | assert(!qubes_pure_validate_file_name((const uint8_t *)"..")); 121 | assert(!qubes_pure_validate_file_name((const uint8_t *)"a/..")); 122 | assert(!qubes_pure_validate_file_name((const uint8_t *)"../a")); 123 | assert(!qubes_pure_validate_file_name((const uint8_t *)"a/../a")); 124 | 125 | // Looks like "." or ".." but is not 126 | assert(qubes_pure_validate_file_name((const uint8_t *)".a")); 127 | assert(qubes_pure_validate_file_name((const uint8_t *)"..a")); 128 | 129 | // Charset validation checks 130 | for (unsigned int i = 0; i < 256; ++i) { 131 | // These bytes are forbidden even if Unicode filtering is off. 132 | bool always_forbidden = i == '/' || i == '.' || i == 0; 133 | // NUL is not a valid continuation byte, so if Unicode filtering 134 | // is on, all non-ASCII bytes are forbidden. 135 | bool bad_unicode = i < 0x20 || i > 0x7E; 136 | unsigned char buf[2] = { i, 0 }; 137 | assert(qubes_pure_validate_file_name_v2(buf, QUBES_PURE_ALLOW_UNSAFE_CHARACTERS) == 138 | (always_forbidden ? -EILSEQ : 0)); 139 | assert(qubes_pure_validate_file_name_v2(buf, 0) == 140 | (always_forbidden || bad_unicode ? -EILSEQ : 0)); 141 | } 142 | 143 | const struct symlink_test checks[] = { 144 | #define TEST(path_, target_, flags_, allowed_) \ 145 | { .path = (path_) \ 146 | , .target = (target_) \ 147 | , .file = __FILE__ \ 148 | , .line = __LINE__ \ 149 | , .flags = (flags_) \ 150 | , .allowed = (allowed_) \ 151 | } 152 | // Symbolic links 153 | // Top level cannot be symlink 154 | TEST("a", "b", 0, false), 155 | TEST("a/b", "../a", 0, false), 156 | TEST("a", "b", 0, false), 157 | // Symbolic links cannot escape 158 | TEST("a/b", "../a", 0, false), 159 | TEST("a/b", "../a", 0, false), 160 | TEST("a/b", "../a/b/c", 0, false), 161 | TEST("a/b", "../a/b/c", 0, false), 162 | TEST("a/b/c", "../../a", 0, false), 163 | TEST("a/b/c", "../../a", 0, false), 164 | TEST("a/b", "a", 0, true), 165 | TEST("a/b/c", "../a", 0, true), 166 | // Absolute symlinks are rejected 167 | TEST("a/b/c", "/a", 0, false), 168 | TEST("a/b/c", "/a", 0, false), 169 | // Symlinks may end in "..". 170 | TEST("a/b/c", "..", 0, true), 171 | // Symlinks may end in "/". 172 | TEST("a/b/c", "a/", 0, true), 173 | // Invalid paths are rejected... 174 | TEST("..", "a/", 0, false), 175 | // ...even with QUBES_PURE_ALLOW_UNSAFE_SYMLINKS. 176 | TEST("..", "a/", QUBES_PURE_ALLOW_UNSAFE_SYMLINKS, false), 177 | // but QUBES_PURE_ALLOW_UNSAFE_SYMLINKS allows bad symlinks 178 | TEST("a", "/home/user/.bashrc", QUBES_PURE_ALLOW_UNSAFE_SYMLINKS, true), 179 | // that are otherwise rejected 180 | TEST("a", "/home/user/.bashrc", 0, false), 181 | // QUBES_PURE_ALLOW_NON_CANONICAL_PATHS allows non-canonical symlinks... 182 | TEST("a/b", "b//c", QUBES_PURE_ALLOW_NON_CANONICAL_PATHS, true), 183 | // ...and non-canonical paths. 184 | TEST("a//b", "b//c", QUBES_PURE_ALLOW_NON_CANONICAL_PATHS, true), 185 | // Symlinks may end in "/"... 186 | TEST("a/b/c", "a/", QUBES_PURE_ALLOW_TRAILING_SLASH, true), 187 | // ...even without QUBES_PURE_ALLOW_TRAILING_SLASH. 188 | TEST("a/b/c", "a/", 0, true), 189 | // but the path cannot... 190 | TEST("a/b/c/", "a/", 0, false), 191 | // ...unless QUBES_PURE_ALLOW_TRAILING_SLASH is passed. 192 | TEST("a/b/c/", "a/", QUBES_PURE_ALLOW_TRAILING_SLASH, true), 193 | }; 194 | int failed = 0; 195 | #define SYMLINK_TEST(a) symlink_test(a, sizeof(a)/sizeof(a[0])) 196 | failed |= SYMLINK_TEST(checks); 197 | 198 | for (int i = 0; i <= QUBES_PURE_ALLOW_NON_CANONICAL_SYMLINKS; 199 | i += QUBES_PURE_ALLOW_NON_CANONICAL_SYMLINKS) { 200 | const struct symlink_test canonical_checks[] = { 201 | // QUBES_PURE_ALLOW_NON_CANONICAL_SYMLINKS allows non-canonical symlinks... 202 | TEST("a/b", "b//c", i, i != 0), 203 | TEST("a/b", "b/./c", i, i != 0), 204 | TEST("a/b", "./c", i, i != 0), 205 | TEST("a/b", "././c", i, i != 0), 206 | TEST("a/b", "././", i, i != 0), 207 | TEST("a/b", "c/./", i, i != 0), 208 | TEST("b/c", "", i, i != 0), 209 | TEST("b/c", ".", i, i != 0), 210 | // ...but not non-canonical paths... 211 | TEST("a//b", "b", i, false), 212 | TEST("a/./b", "b", i, false), 213 | TEST("./b/c", "b", i, false), 214 | // ...or unsafe symlinks 215 | TEST("a/b", "..", i, false), 216 | TEST("a/b", "../b", i, false), 217 | TEST("a/b", "/c", i, false), 218 | TEST("b", "c", i, false), 219 | TEST("b", ".", i, false), 220 | }; 221 | failed |= SYMLINK_TEST(canonical_checks); 222 | } 223 | assert(!failed); 224 | 225 | // Greek letters are safe 226 | assert(qubes_pure_validate_file_name((uint8_t *)u8"\u03b2.txt")); 227 | assert(qubes_pure_validate_file_name((uint8_t *)u8"\u03b1.txt")); 228 | // As are Cyrillic ones 229 | assert(qubes_pure_validate_file_name((uint8_t *)u8"\u0400.txt")); 230 | // As are unicode quotation marks 231 | assert(qubes_pure_validate_file_name((uint8_t *)u8"\u201c")); 232 | // As are ASCII characters, except DEL and controls 233 | for (uint32_t i = 0x20; i < 0x7F; ++i) 234 | character_must_be_allowed(i); 235 | // And CJK ideographs 236 | uint32_t cjk_ranges[] = { 237 | 0x03400, 0x04DBF, 238 | 0x04E00, 0x09FFC, 239 | 0x20000, 0x2A6DD, 240 | 0x2A700, 0x2B734, 241 | 0x2B740, 0x2B81D, 242 | 0x2B820, 0x2CEA1, 243 | 0x2CEB0, 0x2EBE0, 244 | 0x30000, 0x3134A, 245 | 0x0, 246 | }; 247 | for (size_t i = 0; cjk_ranges[i]; i += 2) { 248 | for (uint32_t v = cjk_ranges[i]; v <= cjk_ranges[i + 1]; ++v) { 249 | character_must_be_allowed(v); 250 | } 251 | } 252 | // Forbidden ranges 253 | uint32_t const forbidden[] = { 254 | // C0 controls and empty string 255 | 0x0, 0x1F, 256 | // C1 controls 257 | 0x0007F, 0x0009F, 258 | // Private-use area 259 | 0x0E000, 0x0F8FF, 260 | // Spaces 261 | 0xA0, 0xA0, 262 | 0x02000, 0x0200A, 263 | 0x0205F, 0x0205F, 264 | 0x0180E, 0x0180E, 265 | 0x01680, 0x01680, 266 | // Line breaks 267 | 0x202A, 0x202B, 268 | // Non-characters 269 | 0x0FDD0, 0x0FDEF, 270 | 0x0FFFE, 0x0FFFF, 271 | 0x1FFFE, 0x1FFFF, 272 | 0x2FFFE, 0x2FFFF, 273 | // Forbidden codepoints 274 | 0x0323B0, 0x10FFFF, 275 | // Too long 276 | 0x110000, UINT32_MAX - 1, 277 | 0x0, 278 | }; 279 | for (size_t i = 0; i == 0 || forbidden[i]; i += 2) { 280 | for (uint32_t v = forbidden[i]; v <= forbidden[i + 1]; ++v) { 281 | character_must_be_forbidden(v); 282 | } 283 | } 284 | 285 | // Flags are too complex to display :( 286 | assert(!qubes_pure_string_safe_for_display(u8"\U0001f3f3", 0)); 287 | assert(!qubes_pure_string_safe_for_display(u8"\ufe0f", 0)); 288 | assert(!qubes_pure_string_safe_for_display(u8"\u200d", 0)); 289 | assert(!qubes_pure_string_safe_for_display(u8"\u26a0", 0)); 290 | 291 | // Emojies are not allowed 292 | assert(!qubes_pure_string_safe_for_display(u8"\U0001f642", 0)); 293 | // Cuneiform is way too obscure to be worth the risk 294 | assert(!qubes_pure_string_safe_for_display(u8"\U00012000", 0)); 295 | // Surrogates are forbidden 296 | for (uint32_t i = 0xD800; i <= 0xDFFF; ++i) { 297 | uint8_t buf[4] = { 298 | i >> 12 | 0xE0, 299 | 0x80 | (i >> 6 & 0x3F), 300 | 0x80 | (i & 0x3F), 301 | 0, 302 | }; 303 | assert(buf[0] == 0xED); 304 | assert(buf[1] >= 0xA0 && buf[1] <= 0xBF); 305 | assert(!qubes_pure_string_safe_for_display((char *)buf, 0)); 306 | assert(!qubes_pure_code_point_safe_for_display(i)); 307 | } 308 | 309 | // Invalid codepoints beyond 0x10FFFFF are forbidden 310 | for (uint32_t i = 0x90; i < 0xC0; ++i) { 311 | for (uint32_t j = 0x80; j < 0xC0; ++j) { 312 | for (uint32_t k = 0x80; k < 0xC0; ++k) { 313 | char buf[5] = { 0xF4, i, j, k, 0 }; 314 | assert(!qubes_pure_string_safe_for_display(buf, 0)); 315 | } 316 | } 317 | } 318 | 319 | /* Check for code points that cannot be assigned characters */ 320 | for (uint64_t i = 0; i <= UINT32_MAX >> 0; ++i) { 321 | uint32_t j = (uint32_t)i; 322 | if (j < 32 || j == 0x7F || !U_IS_UNICODE_CHAR(j)) { 323 | assert(!qubes_pure_code_point_safe_for_display(j)); 324 | } else { 325 | assert(j < 0x10FFFFE); 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /rpm_spec/qubes-kernel-vm-support.spec.in: -------------------------------------------------------------------------------- 1 | # 2 | # The Qubes OS Project, http://www.qubes-os.org 3 | # 4 | # Copyright (C) 2015 Marek Marczykowski-Górecki 5 | # 6 | # 7 | # This program is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU General Public License 9 | # as published by the Free Software Foundation; either version 2 10 | # of the License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | # 21 | 22 | Name: qubes-kernel-vm-support 23 | Version: @VERSION@ 24 | Release: 1%{?dist} 25 | Summary: Qubes VM initramfs modules 26 | Source0: qubes-utils-%{version}.tar.gz 27 | 28 | Group: Qubes 29 | Vendor: Invisible Things Lab 30 | License: GPL v2 only 31 | URL: http://www.qubes-os.org 32 | 33 | # qubes-prepare-vm-kernel interface version - to use in kernel.spec 34 | Provides: qubes-prepare-vm-kernel = 2 35 | Requires: dracut 36 | Requires: busybox 37 | Requires: e2fsprogs 38 | BuildRequires: make 39 | BuildRequires: gcc 40 | BuildRequires: e2fsprogs-devel 41 | BuildRequires: pkgconfig(zlib) 42 | %if 0%{?is_opensuse} 43 | # for directory ownership 44 | BuildRequires: dracut 45 | %endif 46 | 47 | %description 48 | This package contains: 49 | 1. Dracut module required to setup Qubes VM root filesystem. This package is 50 | needed in VM only when the VM uses its own kernel (via pvgrub or so). Otherwise 51 | initrd is provided by dom0. 52 | 53 | %prep 54 | %setup -q -n qubes-utils-%{version} 55 | 56 | %build 57 | make -C kernel-modules all 58 | make gptfixer/gpt 59 | 60 | %install 61 | make install-fedora-kernel-support DESTDIR=%{buildroot} SBINDIR=%{_sbindir} 62 | make install-gptfix SBINDIR=%_sbindir DESTDIR=%{buildroot} 63 | 64 | %files 65 | %dir %{_prefix}/lib/qubes 66 | /usr/lib/dracut/modules.d/90qubes-vm 67 | /usr/lib/dracut/modules.d/90qubes-vm-modules 68 | /usr/lib/dracut/modules.d/90qubes-vm-simple 69 | /usr/lib/dracut/modules.d/80xen-scrub-pages 70 | /usr/lib/qubes/vm-modules-genfs 71 | %_sbindir/qubes-prepare-vm-kernel 72 | %_sbindir/gptfix 73 | %config(noreplace) /etc/default/grub.qubes-kernel-vm-support 74 | 75 | %triggerin -- grub2-tools 76 | if ! grep -q '/etc/default/grub\.qubes-kernel-vm-support$' /etc/default/grub 2>/dev/null; then 77 | # do not keep Qubes-related settings directly in user-controlled config, 78 | # include another file 79 | echo '. /etc/default/grub.qubes-kernel-vm-support' >> /etc/default/grub 80 | fi 81 | 82 | %preun 83 | if [ $1 -eq 0 ]; then 84 | if grep -q '/etc/default/grub\.qubes-kernel-vm-support$' /etc/default/grub 2>/dev/null; then 85 | sed -i -e '/grub\.qubes-kernel-vm-support$/d' /etc/default/grub 86 | fi 87 | fi 88 | 89 | %posttrans 90 | 91 | # Rebuild all initramfs images to include updated modules 92 | if [ -r /usr/share/qubes/marker-vm ] && [ -x /usr/bin/dracut ]; then 93 | ret=0 94 | for img in /boot/initramfs-*.img; do 95 | kver="${img#*initramfs-}" 96 | kver="${kver%.img}" 97 | dracut -f "$img" "$kver" || ret=$? 98 | done 99 | if [ "$ret" -eq 0 ]; then 100 | # "milestone" initramfs update version: 101 | # 1 - addition of xen scrub_pages enabling code 102 | echo 1 > /var/lib/qubes/initramfs-updated 103 | fi 104 | fi 105 | 106 | %changelog 107 | @CHANGELOG@ 108 | -------------------------------------------------------------------------------- /rpm_spec/qubes-utils.spec.in: -------------------------------------------------------------------------------- 1 | Name: qubes-utils 2 | Version: @VERSION@ 3 | Release: 1%{?dist} 4 | Summary: Common Linux files for Qubes Dom0 and VM 5 | Source0: %{name}-%{version}.tar.gz 6 | 7 | Group: Qubes 8 | License: GPL 9 | URL: http://www.qubes-os.org 10 | 11 | Requires: udev 12 | Requires: %{name}-libs 13 | Requires: GraphicsMagick 14 | Requires: python%{python3_pkgversion}-qubesimgconverter 15 | Requires: (%{name}-selinux if selinux-policy) 16 | %{?systemd_requires} 17 | %if 0%{?is_opensuse} 18 | BuildRequires: (systemd or systemd-mini) 19 | %else 20 | BuildRequires: systemd 21 | %endif 22 | BuildRequires: python%{python3_pkgversion}-setuptools 23 | BuildRequires: python3-rpm-macros 24 | # for meminfo-writer 25 | BuildRequires: xen-devel 26 | BuildRequires: gcc 27 | BuildRequires: selinux-policy-devel 28 | %if 0%{?is_opensuse} 29 | # for directory ownership 30 | BuildRequires: xen-tools 31 | %endif 32 | 33 | %description 34 | Common Linux files for Qubes Dom0 and VM 35 | 36 | %package -n python%{python3_pkgversion}-qubesimgconverter 37 | Summary: Python package qubesimgconverter 38 | Requires: python%{python3_pkgversion} 39 | Requires: python%{python3_pkgversion}-cairo 40 | %if 0%{?is_opensuse} 41 | Requires: python%{python3_pkgversion}-Pillow 42 | %else 43 | Requires: python%{python3_pkgversion}-pillow 44 | %endif 45 | Requires: python%{python3_pkgversion}-numpy 46 | 47 | %description -n python%{python3_pkgversion}-qubesimgconverter 48 | Python package qubesimgconverter 49 | 50 | %package devel 51 | Summary: Development headers for qubes-utils 52 | Release: 1%{?dist} 53 | Requires: %{name}-libs 54 | 55 | %description devel 56 | Development header and files for qubes-utils 57 | 58 | %package libs 59 | Summary: Qubes utils libraries 60 | Release: 1%{?dist} 61 | BuildRequires: pkgconfig(icu-uc) 62 | BuildRequires: python3 63 | 64 | %description libs 65 | Libraries for qubes-utils 66 | 67 | %package selinux 68 | 69 | BuildRequires: selinux-policy 70 | %{?selinux_requires} 71 | 72 | Summary: SELinux policy for meminfo-writer 73 | License: GPLv2+ 74 | 75 | %description selinux 76 | SELinux policy for meminfo-writer. You need this package to run meminfo-writer 77 | on a system where SELinux is in enforcing mode. 78 | 79 | %pre selinux 80 | %selinux_relabel_pre 81 | 82 | %post selinux 83 | %selinux_modules_install %{_datadir}/selinux/packages/qubes-meminfo-writer.pp || : 84 | 85 | %postun selinux 86 | if [ "$1" -eq 0 ]; then 87 | %selinux_modules_uninstall %{_datadir}/selinux/packages/qubes-meminfo-writer.pp 88 | fi || : 89 | 90 | %posttrans selinux 91 | %selinux_relabel_post 92 | exit 0 93 | 94 | %files selinux 95 | %{_datadir}/selinux/packages/qubes-meminfo-writer.pp 96 | %{_datadir}/selinux/devel/include/contrib/ipp-qubes-meminfo-writer.if 97 | 98 | %prep 99 | %setup -q 100 | 101 | %build 102 | make all selinux BACKEND_VMM=@BACKEND_VMM@ PYTHON=%{__python3} NO_REBUILD_TABLE=1 103 | 104 | %check 105 | make -C qrexec-lib check NO_REBUILD_TABLE=1 106 | %if 0%{?fedora} == 38 107 | make -C qrexec-lib check-table-up-to-date NO_REBUILD_TABLE=1 108 | %endif 109 | 110 | %install 111 | make install install-selinux DESTDIR=%{buildroot} PYTHON=%{__python3} NO_REBUILD_TABLE=1 SBINDIR=%{_sbindir} 112 | 113 | %post 114 | # dom0 115 | %systemd_post qubes-meminfo-writer-dom0.service 116 | # VM 117 | %systemd_post qubes-meminfo-writer.service 118 | 119 | %preun 120 | %systemd_preun qubes-meminfo-writer-dom0.service 121 | %systemd_preun qubes-meminfo-writer.service 122 | 123 | %postun 124 | %systemd_postun_with_restart qubes-meminfo-writer-dom0.service 125 | %systemd_postun_with_restart qubes-meminfo-writer.service 126 | 127 | %post libs -p /sbin/ldconfig 128 | %postun libs -p /sbin/ldconfig 129 | 130 | %clean 131 | rm -rf $RPM_BUILD_ROOT 132 | 133 | %files 134 | %defattr(-,root,root,-) 135 | %_udevrulesdir/*-qubes-*.rules 136 | %_tmpfilesdir/xen-devices-qubes.conf 137 | %dir %{_prefix}/lib/qubes 138 | %{_prefix}/lib/qubes/udev-* 139 | %{_bindir}/meminfo-writer 140 | %{_unitdir}/qubes-meminfo-writer.service 141 | %{_unitdir}/qubes-meminfo-writer-dom0.service 142 | %dir %_includedir/qubes 143 | /etc/xen/scripts/qubes-block 144 | 145 | %files -n python%{python3_pkgversion}-qubesimgconverter 146 | %dir %{python3_sitelib}/qubesimgconverter 147 | %{python3_sitelib}/qubesimgconverter/__init__.py 148 | %{python3_sitelib}/qubesimgconverter/imggen.py 149 | %{python3_sitelib}/qubesimgconverter/test.py 150 | %{python3_sitelib}/qubesimgconverter/test_integ.py 151 | %{python3_sitelib}/qubesimgconverter-*.egg-info 152 | %{python3_sitelib}/qubesimgconverter/__pycache__ 153 | 154 | %files libs 155 | %{_libdir}/libqubes-rpc-filecopy.so.2 156 | %{_libdir}/libqubes-pure.so.0 157 | 158 | %files devel 159 | %defattr(-,root,root,-) 160 | %_includedir/libqubes-rpc-filecopy.h 161 | %dir %_includedir/qubes 162 | %_includedir/qubes/pure.h 163 | %{_libdir}/libqubes-rpc-filecopy.so 164 | %{_libdir}/libqubes-pure.so 165 | 166 | %changelog 167 | @CHANGELOG@ 168 | -------------------------------------------------------------------------------- /selinux/qubes-meminfo-writer.fc: -------------------------------------------------------------------------------- 1 | /usr/bin/meminfo-writer -- gen_context(system_u:object_r:qubes_meminfo_writer_exec_t,s0) 2 | /run/meminfo-writer\.pid -- gen_context(system_u:object_r:qubes_meminfo_writer_var_run_t,s0) 3 | -------------------------------------------------------------------------------- /selinux/qubes-meminfo-writer.if: -------------------------------------------------------------------------------- 1 | 2 | ######################################## 3 | ## 4 | ## Communicate with meminfo-writer 5 | ## 6 | ## 7 | ## 8 | ## Domain allowed access 9 | ## 10 | ## 11 | interface(`qubes_meminfo_writer_signal',` 12 | gen_require(`` 13 | type qubes_meminfo_writer_t, qubes_meminfo_writer_var_run_t; 14 | '')` 15 | allow '$1` qubes_meminfo_writer_t:process signal; 16 | allow '$1` qubes_meminfo_writer_var_run_t:file 'read_file_perms; 17 | ') 18 | -------------------------------------------------------------------------------- /selinux/qubes-meminfo-writer.te: -------------------------------------------------------------------------------- 1 | policy_module(qubes-meminfo-writer, 0.0.1) 2 | 3 | require { 4 | type local_login_t, sysfs_t, proc_t; 5 | } 6 | 7 | type qubes_meminfo_writer_t; 8 | type qubes_meminfo_writer_exec_t; 9 | init_daemon_domain(qubes_meminfo_writer_t, qubes_meminfo_writer_exec_t) 10 | qubes_meminfo_writer_signal(local_login_t) 11 | allow qubes_meminfo_writer_t self:process { fork signal_perms }; 12 | allow qubes_meminfo_writer_t self:fifo_file rw_fifo_file_perms; 13 | allow qubes_meminfo_writer_t { sysfs_t proc_t }:file { open read }; 14 | files_read_etc_files(qubes_meminfo_writer_t) 15 | miscfiles_read_localization(qubes_meminfo_writer_t) 16 | dev_rw_xen(qubes_meminfo_writer_t) 17 | allow qubes_meminfo_writer_t self:unix_stream_socket { connectto create_stream_socket_perms }; 18 | 19 | type qubes_meminfo_writer_var_run_t; 20 | files_pid_file(qubes_meminfo_writer_var_run_t) 21 | allow qubes_meminfo_writer_t var_run_t:dir { add_entry_dir_perms del_entry_dir_perms }; 22 | allow qubes_meminfo_writer_t qubes_meminfo_writer_var_run_t:file { create_file_perms write_file_perms }; 23 | files_pid_filetrans(`qubes_meminfo_writer_t', `qubes_meminfo_writer_var_run_t', `file', `"meminfo-writer.pid"') 24 | -------------------------------------------------------------------------------- /udev/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | 3 | install: 4 | mkdir -p $(DESTDIR)$(SYSLIBDIR)/udev/rules.d 5 | cp udev-qubes-block.rules $(DESTDIR)$(SYSLIBDIR)/udev/rules.d/99-qubes-block.rules 6 | cp udev-qubes-usb.rules $(DESTDIR)$(SYSLIBDIR)/udev/rules.d/99-qubes-usb.rules 7 | cp udev-qubes-misc.rules $(DESTDIR)$(SYSLIBDIR)/udev/rules.d/99-qubes-misc.rules 8 | cp udev-qubes-dmroot.rules $(DESTDIR)$(SYSLIBDIR)/udev/rules.d/90-qubes-dmroot.rules 9 | mkdir -p $(DESTDIR)$(SYSLIBDIR)/tmpfiles.d 10 | cp xen-devices-qubes.conf $(DESTDIR)$(SYSLIBDIR)/tmpfiles.d/xen-devices-qubes.conf 11 | 12 | mkdir -p $(DESTDIR)$(SCRIPTSDIR) 13 | cp udev-block-add-change $(DESTDIR)$(SCRIPTSDIR) 14 | cp udev-block-remove $(DESTDIR)$(SCRIPTSDIR) 15 | cp udev-usb-add-change $(DESTDIR)$(SCRIPTSDIR) 16 | cp udev-usb-remove $(DESTDIR)$(SCRIPTSDIR) 17 | -------------------------------------------------------------------------------- /udev/udev-block-add-change: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | shopt -s nullglob 4 | 5 | 6 | export LC_CTYPE=en_US.UTF-8 7 | NAME=${DEVNAME#/dev/} 8 | DESC="`echo "${ID_MODEL} (${ID_FS_LABEL})" | iconv -f utf8 -t ascii//TRANSLIT`" 9 | SIZE=$[ $(cat /sys/$DEVPATH/size) * 512 ] 10 | MODE=w 11 | QDB_KEY="/qubes-block-devices/$NAME" 12 | 13 | xs_remove() { 14 | if is_attached /sys$DEVPATH; then 15 | return 0 16 | fi 17 | 18 | if qubesdb-read -q "$QDB_KEY/desc" >/dev/null; then 19 | qubesdb-rm "$QDB_KEY/" 20 | qubesdb-write /qubes-block-devices '' 21 | fi 22 | } 23 | 24 | is_used() { 25 | local sys_devpath=$1 26 | local devname=$(grep ^DEVNAME= $sys_devpath/uevent | cut -f 2 -d =) 27 | # mounted; or enabled swap 28 | if lsblk -dnr -o MOUNTPOINT "/dev/$devname" | grep -q .; then 29 | return 0 30 | fi 31 | # part of other device-mapper 32 | if [ -n "`ls -A $sys_devpath/holders 2> /dev/null`" ]; then 33 | return 0 34 | fi 35 | # open device-mapper device 36 | if [ -f "$sys_devpath/dm/name" ] && \ 37 | /sbin/dmsetup info "$(cat $sys_devpath/dm/name)" |\ 38 | grep -q "^Open count:.*[1-9]"; then 39 | return 0 40 | fi 41 | return 1 42 | } 43 | 44 | refresh_another() { 45 | # launch this script for other device 46 | local devpath=$1 47 | local launch_env=$(udevadm info -q all -x -p "$devpath" \ 48 | | grep ^E: | cut -d ' ' -f 2- | tr ' ' ':') 49 | env -i PATH=$PATH $launch_env $0 50 | } 51 | 52 | is_attached() { 53 | dev_hex=$(stat -c %t:%T /dev/$(basename $1)) 54 | if [ -z "$dev_hex" -o "$dev_hex" = "0:0" ]; then 55 | return 1 56 | fi 57 | # looking at sysfs is much faster than looking at xenstore 58 | # this code requires no subprocesses and doesn't hit argument length limitations 59 | for i in /sys/bus/xen-backend/drivers/vbd/vbd-*/physical_device; do 60 | read i_dev_hex < "$i" 61 | if test "$i_dev_hex" == "$dev_hex"; then 62 | return 0 63 | fi 64 | done 65 | return 1 66 | } 67 | 68 | # update info about parent devices, if any: 69 | if [ -f /sys$DEVPATH/partition ]; then 70 | parent=$(dirname $(readlink -f /sys$DEVPATH)) 71 | refresh_another /$(realpath --relative-to=/sys $parent) 72 | # if parent device is already attached, skip its partitions 73 | if is_attached $parent; then 74 | xs_remove 75 | exit 0 76 | fi 77 | fi 78 | 79 | # and underlying devices of device-mapper (if any) 80 | for dev in /sys$DEVPATH/slaves/*; do 81 | refresh_another /$(realpath --relative-to=/sys $dev) 82 | done 83 | 84 | # cache slave devices for remove event 85 | if [ -n "$DM_NAME" ]; then 86 | ls -1 /sys$DEVPATH/slaves/ > /run/qubes/block-slave-cache-$NAME 87 | fi 88 | 89 | # then take care of this device: 90 | 91 | # udev rules already excluded this device: 92 | 93 | if [ -z "$QUBES_EXPORT_BLOCK_DEVICE" ] && [ "$DM_UDEV_DISABLE_DISK_RULES_FLAG" = "1" ]; then 94 | xs_remove 95 | exit 0 96 | fi 97 | 98 | # device itself is already used 99 | if is_used /sys$DEVPATH; then 100 | xs_remove 101 | exit 0 102 | fi 103 | 104 | # or one of its partitions is used 105 | # or already attached (prevent attaching both device and its partition(s) at 106 | # the same time) 107 | for part in /sys$DEVPATH/$NAME*; do 108 | if [ -d $part ]; then 109 | if is_used $part || is_attached $part; then 110 | xs_remove 111 | exit 0 112 | fi 113 | fi 114 | done 115 | 116 | # or "empty" loop device 117 | if [ "$MAJOR" -eq 7 -a ! -d /sys/$DEVPATH/loop ]; then 118 | xs_remove 119 | exit 0 120 | fi 121 | 122 | # or unconnected Network Block Device 123 | if [ "$MAJOR" -eq 43 -a ! -e /sys/$DEVPATH/pid ]; then 124 | xs_remove 125 | exit 0 126 | fi 127 | 128 | # ... and loop devices from excluded directories 129 | if [[ "$NAME" = 'loop'* ]]; then 130 | backing_file=$(cat /sys/block/${NAME}/loop/backing_file) 131 | if [ -n "$backing_file" ]; then 132 | dir_to_check=$(dirname "$backing_file") 133 | while [ "$dir_to_check" != "/" -a "$dir_to_check" != "." ]; do 134 | if [ -e "$dir_to_check/.qubes-exclude-block-devices" ]; then 135 | xs_remove 136 | exit 0 137 | fi 138 | dir_to_check=$(dirname "$dir_to_check") 139 | done 140 | fi 141 | fi 142 | 143 | # Check if device is read-only 144 | if [ "`cat /sys/$DEVPATH/ro`" -eq 1 ]; then 145 | MODE=r 146 | fi 147 | 148 | # Special case for CD 149 | if [ "$ID_TYPE" = "cd" ]; then 150 | if [ "$ID_CDROM_MEDIA" != "1" ]; then 151 | # Hide empty cdrom drive 152 | xs_remove 153 | exit 0 154 | fi 155 | MODE=r 156 | fi 157 | 158 | # Special description for loop devices 159 | if [ -d /sys/$DEVPATH/loop ]; then 160 | DESC=$(cat /sys/$DEVPATH/loop/backing_file) 161 | fi 162 | # and for device-mapper 163 | if [ -n "$DM_NAME" ]; then 164 | DESC="$DM_NAME" 165 | fi 166 | 167 | if [ -f /sys$DEVPATH/partition ]; then 168 | parent=$(basename "$(dirname "$DEVPATH")") 169 | elif echo "$DEVPATH" | grep -q '/host'; then 170 | parent=$(basename "$(sed 's|/host.*$||' <<< "$DEVPATH")") 171 | fi 172 | 173 | # The last one is meant to trigger watches 174 | qubesdb-write \ 175 | "$QDB_KEY/desc" "$DESC" \ 176 | "$QDB_KEY/size" "$SIZE" \ 177 | "$QDB_KEY/mode" "$MODE" \ 178 | "$QDB_KEY/parent" "$parent" \ 179 | /qubes-block-devices '' 180 | 181 | # Make sure that block backend is loaded 182 | /sbin/modprobe xen-blkback 2> /dev/null || /sbin/modprobe blkbk 183 | -------------------------------------------------------------------------------- /udev/udev-block-remove: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NAME=${DEVNAME#/dev/} 4 | QDB_KEY="/qubes-block-devices/$NAME" 5 | # Trailing slash is intentional - it will remove the whole directory, instead of 6 | # a single base entry 7 | qubesdb-rm "$QDB_KEY/" 8 | # This is meant to trigger watches 9 | qubesdb-write /qubes-block-devices '' 10 | 11 | if [ -r /run/qubes/block-slave-cache-$NAME ]; then 12 | # update info about underlying devices of device-mapper (if any); 13 | for dev in $(cat /run/qubes/block-slave-cache-$NAME); do 14 | udevadm trigger /sys/class/block/$dev 15 | done 16 | rm -f /run/qubes/block-slave-cache-$NAME 17 | fi 18 | -------------------------------------------------------------------------------- /udev/udev-qubes-block.rules: -------------------------------------------------------------------------------- 1 | # Expose all (except xen-frontend) block devices via Qubes DB 2 | 3 | # Only block devices are interesting 4 | SUBSYSTEM!="block", GOTO="qubes_block_end" 5 | 6 | # Hide qubes-internal drives from udisks, so file selection dialogs 7 | ENV{MAJOR}=="7", ENV{UDISKS_IGNORE}="1" 8 | KERNEL=="xvda|xvdb|xvdc*|xvdd", ENV{UDISKS_IGNORE}="1" 9 | 10 | # Skip xen-blkfront devices 11 | ENV{MAJOR}=="202", GOTO="qubes_block_end" 12 | 13 | # skip devices excluded elsewhere 14 | ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}=="1", ENV{QUBES_EXPORT_BLOCK_DEVICE}!="1", GOTO="qubes_block_end" 15 | 16 | # Skip device-mapper devices 17 | KERNEL=="dm-*", ENV{DM_NAME}=="snapshot-*", GOTO="qubes_block_end" 18 | KERNEL=="dm-*", ENV{DM_NAME}=="origin-*", GOTO="qubes_block_end" 19 | KERNEL=="dm-*", ENV{DM_NAME}=="", GOTO="qubes_block_end" 20 | 21 | ACTION=="add", RUN+="/usr/lib/qubes/udev-block-add-change" 22 | ACTION=="change", RUN+="/usr/lib/qubes/udev-block-add-change" 23 | ACTION=="remove", RUN+="/usr/lib/qubes/udev-block-remove" 24 | 25 | LABEL="qubes_block_end" 26 | 27 | # vim: set ft=udevrules ff=unix fenc=UTF-8 eol: 28 | -------------------------------------------------------------------------------- /udev/udev-qubes-dmroot.rules: -------------------------------------------------------------------------------- 1 | # Create /dev/mapper/dmroot symlink on TemplateVM/StandaloneVM to make 2 | # grub-mkconfig happy. 3 | # On TemplateBasedVM, it is really a device mapper device. 4 | 5 | SUBSYSTEM=="block", ENV{ID_PART_ENTRY_NAME}=="Root\x20filesystem", ATTR{ro}=="0", SYMLINK+="mapper/dmroot" 6 | -------------------------------------------------------------------------------- /udev/udev-qubes-misc.rules: -------------------------------------------------------------------------------- 1 | # Setup permissions to allow libvchan connections as normal user 2 | KERNEL=="xen/evtchn", MODE="0660", GROUP="qubes" 3 | KERNEL=="xen/gntdev", MODE="0660", GROUP="qubes" 4 | KERNEL=="xen/gntalloc", MODE="0660", GROUP="qubes" 5 | KERNEL=="xen/privcmd", MODE="0660", GROUP="qubes" 6 | KERNEL=="xen/xenbus", MODE="0660", GROUP="qubes" 7 | KERNEL=="xen/hypercall", MODE="0660", GROUP="qubes" 8 | -------------------------------------------------------------------------------- /udev/udev-qubes-usb.rules: -------------------------------------------------------------------------------- 1 | # Expose all USB devices (except block) via Qubes DB 2 | 3 | # Handle only USB devices 4 | SUBSYSTEM!="usb", GOTO="qubes_usb_end" 5 | 6 | # ignore qemu emulated devices in HVM 7 | ENV{ID_VENDOR}=="QEMU", GOTO="qubes_usb_end" 8 | 9 | ACTION=="add", IMPORT{program}="/usr/lib/qubes/udev-usb-add-change" 10 | ACTION=="change", IMPORT{program}="/usr/lib/qubes/udev-usb-add-change" 11 | ACTION=="remove", RUN+="/usr/lib/qubes/udev-usb-remove" 12 | 13 | LABEL="qubes_usb_end" 14 | -------------------------------------------------------------------------------- /udev/udev-usb-add-change: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## 4 | ## This script is invoked by udev rules whenever USB device appears or 5 | ## changes. This happens in usbvm domain (or dom0 if USB controller 6 | ## drivers are in dom0). The script records information about available 7 | ## USB devices into XS directory, making it available to qvm-usb tool 8 | ## running in dom0. 9 | ## 10 | 11 | # FIXME: Ignore USB hubs and other wierd devices (see also in udev-usb-remove). 12 | [ "`echo $TYPE | cut -f1 -d/`" = "9" ] && exit 0 13 | [ "$DEVTYPE" != "usb_device" ] && exit 0 14 | 15 | # qubesdb doesn't allow dot in key name 16 | XSNAME=`basename ${DEVPATH} | tr . _` 17 | 18 | # in general ID_SERIAL = "${ID_VENDOR}_${ID_MODEL}_${ID_SERIAL_SHORT}" 19 | DESC="${ID_VENDOR_ID}:${ID_MODEL_ID} ${ID_VENDOR_ENC} ${ID_MODEL_ENC} ${ID_SERIAL_SHORT}" 20 | 21 | VERSION=`cat /sys/$DEVPATH/version | tr -d ' '|cut -f 1 -d .` 22 | 23 | # ignore usbip-connected devices, as most likely already passed through from 24 | # another VM 25 | if echo $DEVPATH | grep -q /vhci_hcd; then 26 | exit 0 27 | fi 28 | 29 | QDB_KEY="/qubes-usb-devices/$XSNAME" 30 | 31 | # The last one is meant to trigger watches 32 | qubesdb-write \ 33 | "$QDB_KEY/desc" "$DESC" \ 34 | "$QDB_KEY/usb-ver" "$VERSION" \ 35 | "$QDB_KEY/interfaces" "$ID_USB_INTERFACES" \ 36 | /qubes-usb-devices '' 37 | 38 | # Make sure PVUSB backend driver is loaded. 39 | /sbin/modprobe xen-usbback 2> /dev/null || true 40 | -------------------------------------------------------------------------------- /udev/udev-usb-remove: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # FIXME: Ignore USB hubs. 4 | [ "`echo $TYPE | cut -f1 -d/`" = "9" ] && exit 0 5 | 6 | NAME=`basename ${DEVPATH} | tr . _` 7 | QDB_KEY="/qubes-usb-devices/$NAME/" 8 | 9 | qubesdb-rm "$QDB_KEY" 10 | # This is meant to trigger watches 11 | qubesdb-write /qubes-usb-devices '' 12 | -------------------------------------------------------------------------------- /udev/xen-devices-qubes.conf: -------------------------------------------------------------------------------- 1 | # Adjust xen devices permissions early 2 | z /dev/xen/evtchn 0660 - qubes - 3 | z /dev/xen/gntdev 0660 - qubes - 4 | z /dev/xen/gntalloc 0660 - qubes - 5 | z /dev/xen/privcmd 0660 - qubes - 6 | z /dev/xen/xenbus 0660 - qubes - 7 | z /dev/xen/hypercall 0660 - qubes - 8 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 4.3.10 2 | --------------------------------------------------------------------------------