diff --git a/.gitignore b/.gitignore index 9da3db57..fceb360a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ /target/ build/* target/* + +tools/cove_tap_tool/target qemu/ security-monitor/target diff --git a/Makefile b/Makefile index d776f046..b7db7d95 100644 --- a/Makefile +++ b/Makefile @@ -49,16 +49,16 @@ devtools: setup hypervisor: setup devtools PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" ACE_DIR=$(ACE_DIR) $(MAKE) -C hypervisor -confidential_vms: setup devtools hypervisor +confidential_vms: setup devtools hypervisor tools BIN_DIR="$(OVERLAY_ROOT_DIR)/" RELEASE="" $(MAKE) -C $(CONFIDENTIAL_VMS_SOURCE_DIR)/baremetal/ ;\ PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" ACE_DIR=$(ACE_DIR) $(MAKE) -C $(CONFIDENTIAL_VMS_SOURCE_DIR)/linux_vm/ buildroot ;\ - PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" ACE_DIR=$(ACE_DIR) $(MAKE) -C $(CONFIDENTIAL_VMS_SOURCE_DIR)/linux_vm/ overlay ;\ + PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" ACE_DIR=$(ACE_DIR) $(MAKE) -C $(CONFIDENTIAL_VMS_SOURCE_DIR)/linux_vm/ overlay rootfs ;\ PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" ACE_DIR=$(ACE_DIR) $(MAKE) -C hypervisor rootfs; hypervisor_dev: PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" ACE_DIR=$(ACE_DIR) $(MAKE) -C hypervisor dev -dev: +dev: tools $(MAKE) -C $(CONFIDENTIAL_VMS_SOURCE_DIR)/linux_vm/ dev ;\ $(MAKE) -C $(CONFIDENTIAL_VMS_SOURCE_DIR)/linux_vm/ overlay ;\ PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" ACE_DIR=$(ACE_DIR) $(MAKE) -C hypervisor rootfs; @@ -85,7 +85,8 @@ emulator: setup devtools tools: setup mkdir -p $(TOOLS_WORK_DIR) - cp -rf $(TOOLS_SOURCE_DIR)/* $(TOOLS_WORK_DIR) + cp -rf $(TOOLS_SOURCE_DIR)/*.sh $(TOOLS_WORK_DIR)/ + cp -rf $(TOOLS_SOURCE_DIR)/ace $(TOOLS_WORK_DIR)/ verify: rm -rf $(ACE_DIR)/security_monitor/verify/ diff --git a/confidential-vms/linux_vm/Makefile b/confidential-vms/linux_vm/Makefile index c658358a..e310c00e 100644 --- a/confidential-vms/linux_vm/Makefile +++ b/confidential-vms/linux_vm/Makefile @@ -10,18 +10,20 @@ CONFIDENTIAL_VMS_WORK_DIR ?= $(ACE_DIR)/confidential_vms/ CONFIDENTIAL_VMS_LINUX_WORK_DIR ?= $(CONFIDENTIAL_VMS_WORK_DIR)/linux_vm/ CONFIDENTIAL_VMS_LINUX_SOURCE_DIR ?= $(MAKEFILE_SOURCE_DIR) -LINUX_VM_ROOTFS_SOURCE_DIR ?= $(CONFIDENTIAL_VMS_LINUX_SOURCE_DIR)/rootfs +LINUX_VM_ROOTFS_SOURCE_DIR ?= $(CONFIDENTIAL_VMS_LINUX_SOURCE_DIR)/hypervisor_rootfs LINUX_VM_BUILDROOT_SOURCE_DIR ?= $(CONFIDENTIAL_VMS_LINUX_SOURCE_DIR)/../../hypervisor/buildroot LINUX_VM_BUILDROOT_WORK_DIR ?= $(CONFIDENTIAL_VMS_LINUX_WORK_DIR)/buildroot LINUX_VM_BUILDROOT_ROOTFS ?= $(LINUX_VM_BUILDROOT_WORK_DIR)/images/rootfs.ext2 LINUX_VM_BUILDROOT_ROOTFS_SIZE ?= "256M" LINUX_VM_OVERLAY_SOURCE_DIR ?= $(CONFIDENTIAL_VMS_LINUX_SOURCE_DIR)/overlay LINUX_VM_OVERLAY_WORK_DIR ?= $(CONFIDENTIAL_VMS_LINUX_WORK_DIR)/overlay +LINUX_VM_OVERLAY_WORK_ROOT_DIR ?= $(LINUX_VM_OVERLAY_WORK_DIR)/root LINUX_VM_IMAGE ?= $(LINUX_VM_BUILDROOT_WORK_DIR)/images/Image LINUX_VM_KERNEL_CONFIG ?= $(CONFIDENTIAL_VMS_LINUX_SOURCE_DIR)/configurations/linux64-defconfig LINUX_VM_BUILDROOT_CONFIG ?= $(CONFIDENTIAL_VMS_LINUX_SOURCE_DIR)/configurations/qemu_riscv64_virt_defconfig LINUX_VM_BUILDROOT_OVERRIDE_DIR ?= $(CONFIDENTIAL_VMS_LINUX_SOURCE_DIR)/configurations/package_override.dev LINUX_VM_PATCHES_DIR ?= $(CONFIDENTIAL_VMS_LINUX_SOURCE_DIR)/patches/linux/6.3-rc4 +LINUX_DIR ?= $(LINUX_VM_BUILDROOT_WORK_DIR)/build/linux-6.3-rc4/ # overlays HYPERVISOR_OVERLAY_DIR ?= $(ACE_DIR)/hypervisor/overlay/ HYPERVISOR_OVERLAY_ROOT_DIR ?= $(HYPERVISOR_OVERLAY_DIR)/root/ @@ -42,11 +44,12 @@ buildroot: setup echo "Building buildroot"; \ rm -rf $(LINUX_VM_BUILDROOT_WORK_DIR); \ mkdir -p $(LINUX_VM_BUILDROOT_WORK_DIR); \ - mkdir -p $(LINUX_VM_OVERLAY_WORK_DIR); \ + mkdir -p $(LINUX_VM_OVERLAY_WORK_ROOT_DIR); \ + cp $(LINUX_VM_KERNEL_CONFIG) $(LINUX_VM_BUILDROOT_WORK_DIR)/linux64-config; \ cp $(LINUX_VM_BUILDROOT_CONFIG) $(LINUX_VM_BUILDROOT_WORK_DIR)/.config; \ sed "s@^BR2_ROOTFS_OVERLAY=.*@BR2_ROOTFS_OVERLAY=\"$(LINUX_VM_OVERLAY_WORK_DIR)\"@g" -i $(LINUX_VM_BUILDROOT_WORK_DIR)/.config; \ sed "s@^BR2_TARGET_ROOTFS_EXT2_SIZE=.*@BR2_TARGET_ROOTFS_EXT2_SIZE=\"$(LINUX_VM_BUILDROOT_ROOTFS_SIZE)\"@g" -i $(LINUX_VM_BUILDROOT_WORK_DIR)/.config; \ - sed "s@^BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=.*@BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=\"$(LINUX_VM_KERNEL_CONFIG)\"@g" -i $(LINUX_VM_BUILDROOT_WORK_DIR)/.config; \ + sed "s@^BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=.*@BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=\"$(LINUX_VM_BUILDROOT_WORK_DIR)/linux64-config\"@g" -i $(LINUX_VM_BUILDROOT_WORK_DIR)/.config; \ sed "s@^BR2_LINUX_KERNEL_PATCH=.*@BR2_LINUX_KERNEL_PATCH=\"$(LINUX_VM_PATCHES_DIR)\"@g" -i $(LINUX_VM_BUILDROOT_WORK_DIR)/.config; \ $(MAKE) -s -C $(LINUX_VM_BUILDROOT_SOURCE_DIR) RISCV=$(RISCV_GNU_TOOLCHAIN_WORK_DIR) PATH=$(PATH) O=$(LINUX_VM_BUILDROOT_WORK_DIR) CROSS_COMPILE=$(CROSS_COMPILE) BR2_JLEVEL=0 olddefconfig; \ $(MAKE) -s -C $(LINUX_VM_BUILDROOT_SOURCE_DIR) RISCV=$(RISCV_GNU_TOOLCHAIN_WORK_DIR) PATH=$(PATH) O=$(LINUX_VM_BUILDROOT_WORK_DIR) BR2_JLEVEL=0; \ @@ -54,6 +57,7 @@ buildroot: setup dev: echo "Rebuilding buildroot"; \ + sed "s@^BR2_ROOTFS_OVERLAY=.*@BR2_ROOTFS_OVERLAY=\"$(LINUX_VM_OVERLAY_WORK_DIR)\"@g" -i $(LINUX_VM_BUILDROOT_WORK_DIR)/.config; \ sed "s@^BR2_PACKAGE_OVERRIDE_FILE=.*@BR2_PACKAGE_OVERRIDE_FILE=\"$(LINUX_VM_BUILDROOT_OVERRIDE_DIR)\"@g" -i $(LINUX_VM_BUILDROOT_WORK_DIR)/.config ;\ sed "s@^BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=.*@BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=\"$(LINUX_VM_KERNEL_CONFIG)\"@g" -i $(LINUX_VM_BUILDROOT_WORK_DIR)/.config; \ sed "s@^BR2_TARGET_ROOTFS_EXT2_SIZE=.*@BR2_TARGET_ROOTFS_EXT2_SIZE=\"$(LINUX_VM_BUILDROOT_ROOTFS_SIZE)\"@g" -i $(LINUX_VM_BUILDROOT_WORK_DIR)/.config; \ @@ -61,15 +65,20 @@ dev: overlay: setup mkdir -p $(LINUX_VM_OVERLAY_WORK_DIR) ;\ + mkdir -p $(LINUX_VM_OVERLAY_WORK_ROOT_DIR); \ cp -r $(LINUX_VM_OVERLAY_SOURCE_DIR)/* $(LINUX_VM_OVERLAY_WORK_DIR)/ ;\ + PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" $(MAKE) -s -C $(LINUX_VM_OVERLAY_WORK_ROOT_DIR)/ace_module/ RISCV=$(RISCV_GNU_TOOLCHAIN_WORK_DIR) PATH=$(PATH) ARCH=riscv KDIR=$(LINUX_DIR) CROSS_COMPILE=$(CROSS_COMPILE) CC="riscv64-unknown-linux-gnu-gcc" O=$(LINUX_DIR) ;\ mkdir -p $(HYPERVISOR_OVERLAY_ROOT_DIR) ;\ cp $(LINUX_VM_ROOTFS_SOURCE_DIR)/*.sh $(HYPERVISOR_OVERLAY_ROOT_DIR)/ ;\ - rm -rf $(HYPERVISOR_OVERLAY_LINUX_VM_DIR) ;\ - mkdir -p $(HYPERVISOR_OVERLAY_LINUX_VM_DIR) ;\ + rm -rf $(HYPERVISOR_OVERLAY_LINUX_VM_DIR) && mkdir -p $(HYPERVISOR_OVERLAY_LINUX_VM_DIR) ;\ cp -r $(LINUX_VM_IMAGE) $(HYPERVISOR_OVERLAY_LINUX_VM_DIR)/ ;\ cp -r $(LINUX_VM_BUILDROOT_ROOTFS) $(HYPERVISOR_OVERLAY_LINUX_VM_DIR) +rootfs: overlay + PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" $(MAKE) -s -C $(LINUX_VM_BUILDROOT_SOURCE_DIR) RISCV=$(RISCV_GNU_TOOLCHAIN_WORK_DIR) PATH=$(PATH) ARCH=riscv64 KDIR=$(LINUX_DIR) CROSS_COMPILE=$(CROSS_COMPILE) O=$(LINUX_VM_BUILDROOT_WORK_DIR) rootfs-ext2; \ + cp -r $(LINUX_VM_BUILDROOT_WORK_DIR)/images/rootfs.cpio $(HYPERVISOR_OVERLAY_LINUX_VM_DIR); + clean: rm -rf $(ACE_DIR) -.PHONY: all buildroot linux clean overlay rootfs +.PHONY: all buildroot linux clean overlay diff --git a/confidential-vms/linux_vm/configurations/busybox.config b/confidential-vms/linux_vm/configurations/busybox.config new file mode 100644 index 00000000..706f303a --- /dev/null +++ b/confidential-vms/linux_vm/configurations/busybox.config @@ -0,0 +1,6 @@ +CONFIG_MOUNT=y +CONFIG_FEATURE_MOUNT_FSTAB=y +CONFIG_UMOUNT=y +CONFIG_FEATURE_UMOUNT_ALL=y +CONFIG_CRYPTSETUP=y +CONFIG_LOSETUP=y diff --git a/confidential-vms/linux_vm/configurations/linux64-defconfig b/confidential-vms/linux_vm/configurations/linux64-defconfig index 0accb5b0..93653739 100644 --- a/confidential-vms/linux_vm/configurations/linux64-defconfig +++ b/confidential-vms/linux_vm/configurations/linux64-defconfig @@ -186,7 +186,7 @@ CONFIG_CHECKPOINT_RESTORE=y # CONFIG_SYSFS_DEPRECATED is not set # CONFIG_RELAY is not set CONFIG_BLK_DEV_INITRD=y -CONFIG_INITRAMFS_SOURCE="" +# CONFIG_INITRAMFS_SOURCE="" CONFIG_RD_GZIP=y CONFIG_RD_BZIP2=y CONFIG_RD_LZMA=y @@ -326,6 +326,7 @@ CONFIG_FPU=y # CONFIG_RISCV_COVE_HOST is not set # CONFIG_RISCV_COVE_GUEST is not set CONFIG_RISCV_COVE_GUEST=y +CONFIG_RISCV_COVE_GUEST_PROMOTE=y # end of Confidential VM Extension(CoVE) Support # end of Platform type @@ -410,7 +411,7 @@ CONFIG_HAVE_KVM_VCPU_ASYNC_IOCTL=y CONFIG_KVM_XFER_TO_GUEST_WORK=y CONFIG_KVM_GENERIC_HARDWARE_ENABLING=y CONFIG_VIRTUALIZATION=y -CONFIG_KVM=m +CONFIG_KVM=n CONFIG_ARCH_SUPPORTS_ACPI=y CONFIG_ACPI=y CONFIG_ACPI_GENERIC_GSI=y @@ -1613,6 +1614,7 @@ CONFIG_DM_BIO_PRISON=m CONFIG_DM_PERSISTENT_DATA=m # CONFIG_DM_UNSTRIPED is not set # CONFIG_DM_CRYPT is not set +CONFIG_DM_CRYPT=y # CONFIG_DM_SNAPSHOT is not set CONFIG_DM_THIN_PROVISIONING=m # CONFIG_DM_CACHE is not set @@ -4404,7 +4406,7 @@ CONFIG_CRYPTO_RSA=y # # Block ciphers # -CONFIG_CRYPTO_AES=m +CONFIG_CRYPTO_AES=y # CONFIG_CRYPTO_AES_TI is not set # CONFIG_CRYPTO_ANUBIS is not set # CONFIG_CRYPTO_ARIA is not set @@ -4439,6 +4441,7 @@ CONFIG_CRYPTO_CTR=m # CONFIG_CRYPTO_OFB is not set # CONFIG_CRYPTO_PCBC is not set # CONFIG_CRYPTO_XTS is not set +CONFIG_CRYPTO_XTS=y # end of Length-preserving ciphers and modes # @@ -4466,8 +4469,8 @@ CONFIG_CRYPTO_HMAC=m # CONFIG_CRYPTO_POLY1305 is not set # CONFIG_CRYPTO_RMD160 is not set CONFIG_CRYPTO_SHA1=y -CONFIG_CRYPTO_SHA256=m -CONFIG_CRYPTO_SHA512=m +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_SHA512=y # CONFIG_CRYPTO_SHA3 is not set # CONFIG_CRYPTO_SM3_GENERIC is not set # CONFIG_CRYPTO_STREEBOG is not set @@ -4513,8 +4516,10 @@ CONFIG_CRYPTO_JITTERENTROPY=m # CONFIG_CRYPTO_USER_API=y CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_USER_API_SKCIPHER=y # CONFIG_CRYPTO_USER_API_SKCIPHER is not set # CONFIG_CRYPTO_USER_API_RNG is not set +CONFIG_CRYPTO_USER_API_RNG=y # CONFIG_CRYPTO_USER_API_AEAD is not set CONFIG_CRYPTO_USER_API_ENABLE_OBSOLETE=y # end of Userspace interface diff --git a/confidential-vms/linux_vm/configurations/qemu_riscv64_virt_defconfig b/confidential-vms/linux_vm/configurations/qemu_riscv64_virt_defconfig index d7e4851c..f5606e5e 100644 --- a/confidential-vms/linux_vm/configurations/qemu_riscv64_virt_defconfig +++ b/confidential-vms/linux_vm/configurations/qemu_riscv64_virt_defconfig @@ -1,5 +1,5 @@ -BR2_TARGET_GENERIC_HOSTNAME="cvm" -BR2_TARGET_GENERIC_ISSUE="Welcome to Confidential VM!" +BR2_TARGET_GENERIC_HOSTNAME="confidential_vm" +BR2_TARGET_GENERIC_ISSUE="Welcome to ACE Confidential VM!" # Architecture BR2_riscv=y BR2_RISCV_64=y @@ -28,9 +28,9 @@ BR2_TARGET_GENERIC_GETTY=y BR2_TARGET_GENERIC_GETTY_PORT="ttyS0" BR2_TARGET_GENERIC_ROOT_PASSWD="passwd" -# Filesystem -BR2_TARGET_ROOTFS_EXT2=y +# Filesystem BR2_ROOTFS_OVERLAY="" +BR2_TARGET_ROOTFS_EXT2=y BR2_TARGET_ROOTFS_EXT2_2=n BR2_TARGET_ROOTFS_EXT2_3=n BR2_TARGET_ROOTFS_EXT2_4=y @@ -38,6 +38,9 @@ BR2_TARGET_ROOTFS_EXT2_SIZE="5G" # Kernel BR2_LINUX_KERNEL=y +# BR2_LINUX_KERNEL_CUSTOM_GIT=y +# BR2_LINUX_KERNEL_CUSTOM_REPO_URL="" +# BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="" BR2_LINUX_KERNEL_CUSTOM_VERSION=y BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.3-rc4" BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_3=y @@ -52,29 +55,25 @@ BR2_LINUX_KERNEL_PATCH="" # development with custom Linux kernel sources BR2_PACKAGE_OVERRIDE_FILE="" -# Bootloader -# BR2_TARGET_OPENSBI=y -# BR2_TARGET_OPENSBI_USE_PLAT=y -# BR2_TARGET_OPENSBI_PLAT="qemu/virt" - # Packages BR2_PACKAGE_DROPBEAR=y BR2_TARGET_OPENSBI=y BR2_TARGET_OPENSBI_PLAT="generic" -### -# BR2_TOOLCHAIN_BUILDROOT_GLIBC=y -# BR2_TOOLCHAIN_BUILDROOT_CXX=y -# BR2_PACKAGE_HOST_GDB=y -# BR2_PACKAGE_HOST_GDB_TUI=y -# BR2_PACKAGE_HOST_GDB_PYTHON3=y -# BR2_CCACHE=y -# BR2_CCACHE_INITIAL_SETUP="-M0 -F0" -# BR2_SSP_NONE=y - BR2_PER_PACKAGE_DIRECTORIES=y BR2_VERBOSE=0 - - BR2_PACKAGE_NVME=y + +# Initramfs +# BR2_TARGET_ROOTFS_INITRAMFS=y +# BR2_TARGET_ROOTFS_CPIO=y +# BR2_PACKAGE_BUSYBOX=y +# BR2_INIT_BUSYBOX=y +# BR2_PACKAGE_HOST_DRACUT=y +# BR2_TARGET_ROOTFS_CPIO_DRACUT=y +# # dm-crypt +# BR2_PACKAGE_HOST_GENIMAGE=y +# BR2_PACKAGE_CRYPTSETUP=y +# BR2_PACKAGE_HOST_CRYPTSETUP=y +# BR2_PACKAGE_E2FSPROGS=y diff --git a/confidential-vms/linux_vm/rootfs/run_linux_vm.sh b/confidential-vms/linux_vm/hypervisor_rootfs/run_linux_vm.sh similarity index 96% rename from confidential-vms/linux_vm/rootfs/run_linux_vm.sh rename to confidential-vms/linux_vm/hypervisor_rootfs/run_linux_vm.sh index f31e1920..94cc7233 100755 --- a/confidential-vms/linux_vm/rootfs/run_linux_vm.sh +++ b/confidential-vms/linux_vm/hypervisor_rootfs/run_linux_vm.sh @@ -6,6 +6,7 @@ QEMU_CMD=qemu-system-riscv64 KERNEL=/root/linux_vm/Image DRIVE=/root/linux_vm/rootfs.ext2 +INITRAMFS=/root/linux_vm/rootfs.cpio HOST_PORT="$((3000 + RANDOM % 3000))" INTERACTIVE="-nographic" @@ -31,7 +32,7 @@ for i in "$@"; do -m=*|--memory=*) MEMORY="${i#*=}" shift - ;; + ;; --daemonize*) INTERACTIVE="-daemonize" shift @@ -60,4 +61,4 @@ ${QEMU_CMD} ${DEBUG_OPTIONS} \ -drive if=none,format=raw,file=${DRIVE},id=hd0 \ -device virtio-net-pci,netdev=net0,iommu_platform=on,disable-legacy=on,disable-modern=off \ -netdev user,id=net0,net=192.168.100.1/24,dhcpstart=192.168.100.128,hostfwd=tcp::${HOST_PORT}-:22 \ - -nographic + -nographic diff --git a/confidential-vms/linux_vm/overlay/root/ace_module/Makefile b/confidential-vms/linux_vm/overlay/root/ace_module/Makefile new file mode 100644 index 00000000..12d8028b --- /dev/null +++ b/confidential-vms/linux_vm/overlay/root/ace_module/Makefile @@ -0,0 +1,27 @@ +CONFIG_MODULE_SIG=n + +startstop-objs := start.o stop.o +obj-m += ace.o + +ifeq ($(CONFIG_STATUS_CHECK_GCC),y) + CC=$(STATUS_CHECK_GCC) + ccflags-y += -fanalyzer +endif + +KDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(CURDIR) + +default: + $(MAKE) -C $(KDIR) CC="$(CC)" M="$(PWD)" modules + rm -f .Module* .modules* .ace* *.symvers *.order ace.mod.* *.o *.mod + +load: + @insmod ace.ko + +clean: + $(MAKE) -C /lib/modules/$(shell uname -r)/build CC="$(CC)" M="$(PWD)" clean + $(RM) other/cat_noblock *.plist + +indent: + clang-format -i *[.ch] + clang-format -i other/*[.ch] \ No newline at end of file diff --git a/confidential-vms/linux_vm/overlay/root/ace_module/ace.c b/confidential-vms/linux_vm/overlay/root/ace_module/ace.c new file mode 100644 index 00000000..287ea693 --- /dev/null +++ b/confidential-vms/linux_vm/overlay/root/ace_module/ace.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +#include /* usleep_range */ +#include +#include /* single_open, single_release */ +#include /* kmalloc, kfree */ + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Wojciech Ozga "); + +static int ace_init(void){ + volatile u8 *secret; + struct sbiret ret; + int i; + + printk(KERN_ALERT "Requesting secret from the security monitor\n"); + secret = kmalloc(1024*sizeof(u8), GFP_KERNEL); + ret = sbi_ecall(0x434F5647, 9, virt_to_phys((void *) secret), 1024, 0, 0, 0, 0); + if (!ret.error) { + printk(KERN_ALERT "Secret=0x"); + for (i=0; i - step/bucket size as a power of 2 for - statistical time based profiling. - -+ promote_to_cove_guest= [RISCV] Isolate VM from the hypervisor. -+ Format: -+ - prompt_ramdisk= [RAM] [Deprecated] - - prot_virt= [S390] enable hosting protected virtual machines +diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig +index 2ca9e01ad0e8..317e0f41ac9b 100644 +--- a/arch/riscv/Kconfig ++++ b/arch/riscv/Kconfig +@@ -526,6 +526,13 @@ config RISCV_COVE_GUEST + help + Enables support for running TVMs on platforms supporting CoVE. + ++config RISCV_COVE_GUEST_PROMOTE ++ bool "Automatic promotion of VM to TVM for Confidential VM Extension(CoVE)" ++ default n ++ select RISCV_COVE_GUEST ++ help ++ VM requests promotion to TVM during early boot on platforms supporting CoVE. ++ + endmenu # "Confidential VM Extension(CoVE) Support" + + endmenu # "Platform type" diff --git a/arch/riscv/cove/core.c b/arch/riscv/cove/core.c -index 582feb1c6c8d..732434279b34 100644 +index 582feb1c6c8d..16d607113366 100644 --- a/arch/riscv/cove/core.c +++ b/arch/riscv/cove/core.c -@@ -38,3 +38,24 @@ void riscv_cove_sbi_init(void) - if (sbi_probe_extension(SBI_EXT_COVG) > 0) - is_tvm = true; - } +@@ -13,6 +13,8 @@ + #include + #include + ++extern char __cove_tap_start[]; + -+int promote_to_cove_guest(char *boot_command_line, unsigned long fdt_address) -+{ -+ struct sbiret ret; -+ int rc = 0; + static bool is_tvm; + + bool is_cove_guest(void) +diff --git a/arch/riscv/include/asm/cove_promote.h b/arch/riscv/include/asm/cove_promote.h +new file mode 100644 +index 000000000000..1472cbaf071f +--- /dev/null ++++ b/arch/riscv/include/asm/cove_promote.h +@@ -0,0 +1,19 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Definitions for promotion of VM to TVM. ++ * ++ * Copyright (c) 2024 IBM Corp. ++ * ++ * Authors: ++ * Wojciech Ozga ++ */ + -+ if (strstr(boot_command_line, "promote_to_cove_guest")) { -+ ret = sbi_ecall(SBI_EXT_COVH, SBI_EXT_COVH_PROMOTE_TO_TVM, fdt_address, -+ 0, 0, 0, 0, 0); -+ if (ret.error) { -+ rc = sbi_err_map_linux_errno(ret.error); -+ goto done; -+ } -+ } -+ pr_info("Promotion to CoVE guest succeeded\n"); -+ return rc; ++#ifndef __RISCV_COVE_PROMOTE_H__ ++#define __RISCV_COVE_PROMOTE_H__ + -+done: -+ pr_err("Promotion to CoVE guest failed %d\n", rc); -+ return rc; -+} -\ No newline at end of file -diff --git a/arch/riscv/include/asm/cove.h b/arch/riscv/include/asm/cove.h -index c4d609d64150..59aba7f9061f 100644 ---- a/arch/riscv/include/asm/cove.h -+++ b/arch/riscv/include/asm/cove.h -@@ -14,6 +14,7 @@ - #ifdef CONFIG_RISCV_COVE_GUEST - void riscv_cove_sbi_init(void); - bool is_cove_guest(void); -+int promote_to_cove_guest(char *boot_command_line, unsigned long fdt_address); - #else /* CONFIG_RISCV_COVE_GUEST */ - static inline bool is_cove_guest(void) - { -@@ -22,6 +23,11 @@ static inline bool is_cove_guest(void) - static inline void riscv_cove_sbi_init(void) - { - } -+static inline int promote_to_cove_guest(char *boot_command_line, -+ unsigned long fdt_address) -+{ -+ return 0; -+} - #endif /* CONFIG_RISCV_COVE_GUEST */ - - #endif /* __RISCV_COVE_H__ */ ++#ifdef CONFIG_RISCV_COVE_GUEST_PROMOTE ++#define COVE_PROMOTE_SBI_EXT_ID 0x434F5648 ++#define COVE_PROMOTE_SBI_FID 0x15 ++#endif /* CONFIG_RISCV_COVE_GUEST_PROMOTE */ ++ ++#endif /* __RISCV_COVE_PROMOTE_H__ */ diff --git a/arch/riscv/include/asm/kvm_cove.h b/arch/riscv/include/asm/kvm_cove.h -index afaea7c621bb..561da58376ac 100644 +index afaea7c621bb..f9ce258dfab8 100644 --- a/arch/riscv/include/asm/kvm_cove.h +++ b/arch/riscv/include/asm/kvm_cove.h -@@ -19,6 +19,10 @@ +@@ -19,6 +19,13 @@ #include #include -+DECLARE_STATIC_KEY_FALSE(kvm_riscv_covi_available); -+#define kvm_riscv_covi_available() \ -+ static_branch_unlikely(&kvm_riscv_covi_available) ++#define KVM_COVE_TSM_CAP_PROMOTE_TVM 0x0 ++#define KVM_COVE_TSM_CAP_ATTESTATION_LOCAL 0x1 ++#define KVM_COVE_TSM_CAP_ATTESTATION_REMOTE 0x2 ++#define KVM_COVE_TSM_CAP_AIA 0x3 ++#define KVM_COVE_TSM_CAP_MRIF 0x4 ++#define KVM_COVE_TSM_CAP_MEMORY_ALLOCATION 0x5 + #define KVM_COVE_PAGE_SIZE_4K (1UL << 12) #define KVM_COVE_PAGE_SIZE_2MB (1UL << 21) #define KVM_COVE_PAGE_SIZE_1GB (1UL << 30) -@@ -130,7 +134,10 @@ int kvm_riscv_cove_init(void); +@@ -126,11 +133,15 @@ static inline bool is_cove_vcpu(struct kvm_vcpu *vcpu) + #ifdef CONFIG_RISCV_COVE_HOST + + bool kvm_riscv_cove_enabled(void); ++bool kvm_riscv_cove_capability(unsigned long cap); + int kvm_riscv_cove_init(void); /* TVM related functions */ void kvm_riscv_cove_vm_destroy(struct kvm *kvm); @@ -93,7 +89,14 @@ index afaea7c621bb..561da58376ac 100644 /* TVM VCPU related functions */ void kvm_riscv_cove_vcpu_destroy(struct kvm_vcpu *vcpu); -@@ -164,7 +171,13 @@ static inline int kvm_riscv_cove_hardware_enable(void) {return 0; } +@@ -158,13 +169,20 @@ int kvm_riscv_cove_aia_convert_imsic(struct kvm_vcpu *vcpu, phys_addr_t imsic_pa + int kvm_riscv_cove_vcpu_imsic_addr(struct kvm_vcpu *vcpu); + #else + static inline bool kvm_riscv_cove_enabled(void) {return false; }; ++static inline bool kvm_riscv_cove_capability(unsigned long cap) { return false; }; + static inline int kvm_riscv_cove_init(void) { return -1; } + static inline void kvm_riscv_cove_hardware_disable(void) {} + static inline int kvm_riscv_cove_hardware_enable(void) {return 0; } /* TVM related functions */ static inline void kvm_riscv_cove_vm_destroy(struct kvm *kvm) {} @@ -136,7 +139,7 @@ index 5b37a12337b1..763a931407f3 100644 #endif /* __RISCV_KVM_VCPU_SBI_H__ */ diff --git a/arch/riscv/include/asm/sbi.h b/arch/riscv/include/asm/sbi.h -index 03b0cc871242..c48fa25a24b4 100644 +index 03b0cc871242..01e9e5b1d7a2 100644 --- a/arch/riscv/include/asm/sbi.h +++ b/arch/riscv/include/asm/sbi.h @@ -374,6 +374,7 @@ enum sbi_ext_covh_fid { @@ -147,6 +150,22 @@ index 03b0cc871242..c48fa25a24b4 100644 }; enum sbi_ext_covi_fid { +@@ -410,9 +411,15 @@ struct sbi_cove_tsm_info { + /* Current state of the TSM */ + enum sbi_cove_tsm_state tstate; + ++ /* TSM implementation identifier */ ++ uint32_t impl_id; ++ + /* Version of the loaded TSM */ + uint32_t version; + ++ /* Capabilities of the TSM */ ++ unsigned long capabilities; ++ + /* Number of 4K pages required per TVM */ + unsigned long tvm_pages_needed; + diff --git a/arch/riscv/include/uapi/asm/kvm.h b/arch/riscv/include/uapi/asm/kvm.h index 2a2434136e39..679a6727a143 100644 --- a/arch/riscv/include/uapi/asm/kvm.h @@ -159,26 +178,82 @@ index 2a2434136e39..679a6727a143 100644 KVM_RISCV_SBI_EXT_MAX, }; -diff --git a/arch/riscv/kernel/setup.c b/arch/riscv/kernel/setup.c -index 20b028090cb1..440b4f838342 100644 ---- a/arch/riscv/kernel/setup.c -+++ b/arch/riscv/kernel/setup.c -@@ -36,6 +36,7 @@ - #include - #include - #include -+#include - - #include "head.h" - -@@ -267,6 +268,7 @@ static void __init parse_dtb(void) - void __init setup_arch(char **cmdline_p) - { - parse_dtb(); -+ promote_to_cove_guest(boot_command_line, dtb_early_pa); - setup_initial_init_mm(_stext, _etext, _edata, _end); - - *cmdline_p = boot_command_line; +diff --git a/arch/riscv/kernel/head.S b/arch/riscv/kernel/head.S +index 4bf6c449d78b..bc7b9aacea51 100644 +--- a/arch/riscv/kernel/head.S ++++ b/arch/riscv/kernel/head.S +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include "efi-header.S" + + __HEAD +@@ -198,6 +199,18 @@ ENTRY(_start_kernel) + csrw CSR_IE, zero + csrw CSR_IP, zero + ++#ifdef CONFIG_RISCV_COVE_GUEST_PROMOTE ++ mv s1, a0 ++ mv s2, a1 ++ li a7, COVE_PROMOTE_SBI_EXT_ID ++ li a6, COVE_PROMOTE_SBI_FID ++ mv a0, a1 ++ la a1, __cove_tap_start ++ ecall ++ mv a0, s1 ++ mv a1, s2 ++#endif ++ + #ifdef CONFIG_RISCV_M_MODE + /* flush the instruction cache */ + fence.i +diff --git a/arch/riscv/kernel/vmlinux-xip.lds.S b/arch/riscv/kernel/vmlinux-xip.lds.S +index eab9edc3b631..e8b14457d2be 100644 +--- a/arch/riscv/kernel/vmlinux-xip.lds.S ++++ b/arch/riscv/kernel/vmlinux-xip.lds.S +@@ -58,6 +58,17 @@ SECTIONS + } + _exiprom = .; /* End of XIP ROM area */ + ++#ifdef CONFIG_RISCV_COVE_GUEST ++ . = ALIGN(4096); ++ .cove_tvm_attestation_payload : { ++ __cove_tap_start = .; ++ LONG(0xace0ace0) ++ SHORT(0x0FFA) ++ FILL(0x00) ++ . += 4090; ++ __cove_tap_end = .; ++ } ++#endif + + /* + * From this point, stuff is considered writable and will be copied to RAM +diff --git a/arch/riscv/kernel/vmlinux.lds.S b/arch/riscv/kernel/vmlinux.lds.S +index 53a8ad65b255..a2af65f95034 100644 +--- a/arch/riscv/kernel/vmlinux.lds.S ++++ b/arch/riscv/kernel/vmlinux.lds.S +@@ -113,6 +113,18 @@ SECTIONS + } + __init_end = .; + ++#ifdef CONFIG_RISCV_COVE_GUEST ++ . = ALIGN(4096); ++ .cove_tvm_attestation_payload : { ++ __cove_tap_start = .; ++ LONG(0xace0ace0) ++ SHORT(0x0FFA) ++ FILL(0x00) ++ . += 4090; ++ __cove_tap_end = .; ++ } ++#endif ++ + /* Start of data section */ + _sdata = .; + RO_DATA(SECTION_ALIGN) diff --git a/arch/riscv/kvm/Makefile b/arch/riscv/kvm/Makefile index 31f4dbd97b03..fba7ebd0cd72 100644 --- a/arch/riscv/kvm/Makefile @@ -189,23 +264,23 @@ index 31f4dbd97b03..fba7ebd0cd72 100644 kvm-y += aia_imsic.o -kvm-$(CONFIG_RISCV_COVE_HOST) += cove_sbi.o cove.o vcpu_sbi_covg.o +kvm-$(CONFIG_RISCV_COVE_HOST) += cove_sbi.o cove.o vcpu_sbi_covg.o vcpu_sbi_covh.o -diff --git a/arch/riscv/kvm/aia.c b/arch/riscv/kvm/aia.c -index 88b91b5d5837..3259d53197ac 100644 ---- a/arch/riscv/kvm/aia.c -+++ b/arch/riscv/kvm/aia.c -@@ -30,6 +30,7 @@ static int hgei_parent_irq; - unsigned int kvm_riscv_aia_nr_hgei; - unsigned int kvm_riscv_aia_max_ids; - DEFINE_STATIC_KEY_FALSE(kvm_riscv_aia_available); -+DEFINE_STATIC_KEY_FALSE(kvm_riscv_covi_available); - - static int aia_find_hgei(struct kvm_vcpu *owner) - { diff --git a/arch/riscv/kvm/cove.c b/arch/riscv/kvm/cove.c -index ba596b7f2240..8dca1b951c39 100644 +index ba596b7f2240..ed7dc0fbf08b 100644 --- a/arch/riscv/kvm/cove.c +++ b/arch/riscv/kvm/cove.c -@@ -589,9 +589,9 @@ void noinstr kvm_riscv_cove_vcpu_switchto(struct kvm_vcpu *vcpu, struct kvm_cpu_ +@@ -150,6 +150,11 @@ __always_inline bool kvm_riscv_cove_enabled(void) + return riscv_cove_enabled; + } + ++__always_inline bool kvm_riscv_cove_capability(unsigned long cap) ++{ ++ return tinfo.capabilities & BIT(cap); ++} ++ + static void kvm_cove_imsic_clone(void *info) + { + int rc; +@@ -589,9 +594,9 @@ void noinstr kvm_riscv_cove_vcpu_switchto(struct kvm_vcpu *vcpu, struct kvm_cpu_ /* * Bind the vsfile here instead during the new vsfile allocation because @@ -213,11 +288,11 @@ index ba596b7f2240..8dca1b951c39 100644 + * COVI bind call requires the TVM to be in finalized state. */ - if (tvcpuc->imsic.bind_required) { -+ if (likely(kvm_riscv_covi_available()) && tvcpuc->imsic.bind_required) { ++ if (kvm_riscv_cove_capability(KVM_COVE_TSM_CAP_AIA) && tvcpuc->imsic.bind_required) { tvcpuc->imsic.bind_required = false; rc = kvm_riscv_cove_vcpu_imsic_bind(vcpu, BIT(tvcpuc->imsic.vsfile_hgei)); if (rc) { -@@ -628,12 +628,12 @@ void kvm_riscv_cove_vcpu_destroy(struct kvm_vcpu *vcpu) +@@ -628,12 +633,12 @@ void kvm_riscv_cove_vcpu_destroy(struct kvm_vcpu *vcpu) int kvm_riscv_cove_vcpu_init(struct kvm_vcpu *vcpu) { @@ -233,63 +308,49 @@ index ba596b7f2240..8dca1b951c39 100644 if (!vcpu) return -EINVAL; -@@ -654,36 +654,38 @@ int kvm_riscv_cove_vcpu_init(struct kvm_vcpu *vcpu) +@@ -654,15 +659,20 @@ int kvm_riscv_cove_vcpu_init(struct kvm_vcpu *vcpu) if (!tvcpuc) return -ENOMEM; - vcpus_page = alloc_pages(GFP_KERNEL | __GFP_ZERO, -- get_order_num_pages(tinfo.tvcpu_pages_needed)); -- if (!vcpus_page) { -- rc = -ENOMEM; -- goto alloc_page_failed; -- } ++ tvcpuc->vcpu = vcpu; ++ tvcpuc->vcpu_state.npages = tinfo.tvcpu_pages_needed; ++ vcpu->arch.tc = tvcpuc; ++ ++ if (!kvm_riscv_cove_capability(KVM_COVE_TSM_CAP_MEMORY_ALLOCATION)) { ++ return 0; ++ } ++ ++ vcpus_page = alloc_pages(GFP_KERNEL | __GFP_ZERO, + get_order_num_pages(tinfo.tvcpu_pages_needed)); + if (!vcpus_page) { + rc = -ENOMEM; + goto alloc_page_failed; + } - - tvcpuc->vcpu = vcpu; - tvcpuc->vcpu_state.npages = tinfo.tvcpu_pages_needed; -- tvcpuc->vcpu_state.page = vcpus_page; -- vcpus_phys_addr = page_to_phys(vcpus_page); - -- rc = cove_convert_pages(vcpus_phys_addr, tvcpuc->vcpu_state.npages, true); -- if (rc) -- goto convert_failed; -+ if (tinfo.tvcpu_pages_needed > 0) { -+ vcpus_page = alloc_pages(GFP_KERNEL | __GFP_ZERO, get_order_num_pages(tinfo.tvcpu_pages_needed)); -+ if (!vcpus_page) { -+ rc = -ENOMEM; -+ goto alloc_page_failed; -+ } -+ tvcpuc->vcpu_state.page = vcpus_page; -+ vcpus_phys_addr = page_to_phys(vcpus_page); - -- rc = sbi_covh_create_tvm_vcpu(tvmc->tvm_guest_id, vcpu->vcpu_idx, vcpus_phys_addr); -- if (rc) -- goto vcpu_create_failed; -+ rc = cove_convert_pages(vcpus_phys_addr, tvcpuc->vcpu_state.npages, true); -+ if (rc) -+ goto convert_failed; +- tvcpuc->vcpu = vcpu; +- tvcpuc->vcpu_state.npages = tinfo.tvcpu_pages_needed; + tvcpuc->vcpu_state.page = vcpus_page; + vcpus_phys_addr = page_to_phys(vcpus_page); -+ rc = sbi_covh_create_tvm_vcpu(tvmc->tvm_guest_id, vcpu->vcpu_idx, vcpus_phys_addr); -+ if (rc) -+ goto vcpu_create_failed; -+ } - vcpu->arch.tc = tvcpuc; +@@ -674,8 +684,6 @@ int kvm_riscv_cove_vcpu_init(struct kvm_vcpu *vcpu) + if (rc) + goto vcpu_create_failed; +- vcpu->arch.tc = tvcpuc; +- return 0; vcpu_create_failed: - /* Reclaim all the pages or return to the confidential page pool */ -- sbi_covh_tsm_reclaim_pages(vcpus_phys_addr, tvcpuc->vcpu_state.npages); -+ if (tinfo.tvcpu_pages_needed > 0) -+ sbi_covh_tsm_reclaim_pages(vcpus_phys_addr, tvcpuc->vcpu_state.npages); - - convert_failed: -- __free_pages(vcpus_page, get_order_num_pages(tinfo.tvcpu_pages_needed)); -+ if (tinfo.tvcpu_pages_needed > 0) -+ __free_pages(vcpus_page, get_order_num_pages(tinfo.tvcpu_pages_needed)); +@@ -686,6 +694,7 @@ int kvm_riscv_cove_vcpu_init(struct kvm_vcpu *vcpu) + __free_pages(vcpus_page, get_order_num_pages(tinfo.tvcpu_pages_needed)); alloc_page_failed: ++ vcpu->arch.tc = NULL; kfree(tvcpuc); -@@ -877,7 +879,7 @@ void kvm_riscv_cove_vm_destroy(struct kvm *kvm) + return rc; + } +@@ -877,7 +886,7 @@ void kvm_riscv_cove_vm_destroy(struct kvm *kvm) kvm_err("Memory reclaim failed with rc %d\n", rc); } @@ -298,7 +359,18 @@ index ba596b7f2240..8dca1b951c39 100644 { struct kvm_cove_tvm_context *tvmc; struct page *tvms_page, *pgt_page; -@@ -980,6 +982,64 @@ int kvm_riscv_cove_vm_init(struct kvm *kvm) +@@ -885,6 +894,10 @@ int kvm_riscv_cove_vm_init(struct kvm *kvm) + unsigned long gstage_pgd_size = kvm_riscv_gstage_pgd_size(); + int rc = 0; + ++ // Multi-step TVM creation requires TSM that supports dynamic page conversion ++ if (!kvm_riscv_cove_capability(KVM_COVE_TSM_CAP_MEMORY_ALLOCATION)) ++ return -EOPNOTSUPP; ++ + tvmc = kzalloc(sizeof(*tvmc), GFP_KERNEL); + if (!tvmc) + return -ENOMEM; +@@ -980,6 +993,65 @@ int kvm_riscv_cove_vm_init(struct kvm *kvm) return rc; } @@ -313,6 +385,9 @@ index ba596b7f2240..8dca1b951c39 100644 + void *nshmem = nacl_shmem(); + int rc = 0, gpr_id, offset; + ++ if (!kvm_riscv_cove_capability(KVM_COVE_TSM_CAP_PROMOTE_TVM)) ++ return -EOPNOTSUPP; ++ + tvmc = kzalloc(sizeof(*tvmc), GFP_KERNEL); + if (!tvmc) + return -ENOMEM; @@ -342,8 +417,6 @@ index ba596b7f2240..8dca1b951c39 100644 + goto vcpus_allocated; + + target_vcpu->requests = 0; -+ if (target_vcpu->vcpu_idx != 0) -+ kvm_riscv_vcpu_power_off(target_vcpu); + } + + tvmc->finalized_done = true; @@ -363,17 +436,6 @@ index ba596b7f2240..8dca1b951c39 100644 int kvm_riscv_cove_init(void) { int rc; -@@ -988,6 +1048,10 @@ int kvm_riscv_cove_init(void) - if (sbi_probe_extension(SBI_EXT_COVH) <= 0 || !kvm_riscv_nacl_available()) - return -EOPNOTSUPP; - -+ if (sbi_probe_extension(SBI_EXT_COVI) > 0) { -+ static_branch_enable(&kvm_riscv_covi_available); -+ } -+ - rc = sbi_covh_tsm_get_info(&tinfo); - if (rc < 0) - return -EINVAL; diff --git a/arch/riscv/kvm/cove_sbi.c b/arch/riscv/kvm/cove_sbi.c index 4759b4920226..2325ee0f2a15 100644 --- a/arch/riscv/kvm/cove_sbi.c @@ -403,7 +465,7 @@ index 4759b4920226..2325ee0f2a15 100644 + return rc; +} diff --git a/arch/riscv/kvm/main.c b/arch/riscv/kvm/main.c -index a05941420307..bba0d87c7aa8 100644 +index a05941420307..bb5562ecc97c 100644 --- a/arch/riscv/kvm/main.c +++ b/arch/riscv/kvm/main.c @@ -31,12 +31,11 @@ int kvm_arch_hardware_enable(void) @@ -419,7 +481,7 @@ index a05941420307..bba0d87c7aa8 100644 + * mode, we need to initialize other CSRs as well for legacy VMs. */ - if (unlikely(kvm_riscv_cove_enabled())) -+ if (unlikely(kvm_riscv_cove_enabled()) && kvm_riscv_covi_available()) ++ if (unlikely(kvm_riscv_cove_enabled()) && kvm_riscv_cove_capability(KVM_COVE_TSM_CAP_AIA)) goto enable_aia; hedeleg = 0; @@ -482,7 +544,7 @@ index b007c027baed..5a3ef6ea01e9 100644 d.addr, d.size, d.order); else diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c -index 005c7c93536d..4d8a01385ed4 100644 +index 005c7c93536d..62153d6ca579 100644 --- a/arch/riscv/kvm/vcpu.c +++ b/arch/riscv/kvm/vcpu.c @@ -730,8 +730,8 @@ long kvm_arch_vcpu_async_ioctl(struct file *filp, @@ -492,7 +554,7 @@ index 005c7c93536d..4d8a01385ed4 100644 - /* We do not support user space emulated IRQCHIP for TVMs yet */ - if (is_cove_vcpu(vcpu)) + /* We do not support user space emulated IRQCHIP for TVMs that utilize AIA yet */ -+ if (is_cove_vcpu(vcpu) && kvm_riscv_aia_initialized(vcpu->kvm)) ++ if (is_cove_vcpu(vcpu) && kvm_riscv_cove_capability(KVM_COVE_TSM_CAP_AIA)) return -ENXIO; if (copy_from_user(&irq, argp, sizeof(irq))) @@ -524,7 +586,7 @@ index 005c7c93536d..4d8a01385ed4 100644 + * Do not update HVIP CSR for TVMs with AIA because AIA + * provides alternative method to inject interrupts. + */ -+ if (!is_cove_vcpu(vcpu) || !kvm_riscv_covi_available()) ++ if (!is_cove_vcpu(vcpu) || !kvm_riscv_cove_capability(KVM_COVE_TSM_CAP_AIA)) kvm_riscv_update_hvip(vcpu); if (ret <= 0 || @@ -703,10 +765,15 @@ index 000000000000..ef3e255732b4 + .handler = kvm_sbi_ext_covh_handler, +}; diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c -index 8a1460dba76c..c9d8d2b86609 100644 +index 8a1460dba76c..6df73ea8e16f 100644 --- a/arch/riscv/kvm/vm.c +++ b/arch/riscv/kvm/vm.c -@@ -48,11 +48,11 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) +@@ -44,15 +44,15 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) + + if (unlikely(type == KVM_VM_TYPE_RISCV_COVE)) { + if (!kvm_riscv_cove_enabled()) { +- kvm_err("Unable to init CoVE VM because cove is not enabled\n"); ++ kvm_err("Unable to init CoVE VM because CoVE extension is not enabled\n"); return -EPERM; } @@ -772,15 +839,22 @@ index 8523c508c3a5..498fbf5c6c9b 100644 EXPORT_SYMBOL_GPL(set_memory_decrypted); diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c -index d1a68b6d03b3..3d586b02bb6b 100644 +index d1a68b6d03b3..46e2ce22c729 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c -@@ -1169,7 +1169,7 @@ int __init early_init_dt_scan_chosen(char *cmdline) +@@ -1167,6 +1167,7 @@ int __init early_init_dt_scan_chosen(char *cmdline) + early_init_dt_check_for_initrd(node); + early_init_dt_check_for_elfcorehdr(node); ++#ifndef CONFIG_RISCV_COVE_GUEST rng_seed = of_get_flat_dt_prop(node, "rng-seed", &l); if (rng_seed && l > 0) { -- add_bootloader_randomness(rng_seed, l); -+ // add_bootloader_randomness(rng_seed, l); + add_bootloader_randomness(rng_seed, l); +@@ -1178,6 +1179,7 @@ int __init early_init_dt_scan_chosen(char *cmdline) + of_fdt_crc32 = crc32_be(~0, initial_boot_params, + fdt_totalsize(initial_boot_params)); + } ++#endif - /* try to clear seed so it won't be found. */ - fdt_nop_property(initial_boot_params, node, "rng-seed"); + /* Retrieve command line */ + p = of_get_flat_dt_prop(node, "bootargs", &l); \ No newline at end of file diff --git a/security-monitor/Cargo.toml b/security-monitor/Cargo.toml index e8dd93fb..9bd2f520 100644 --- a/security-monitor/Cargo.toml +++ b/security-monitor/Cargo.toml @@ -10,7 +10,7 @@ edition = "2021" # and when promoting a VM to a confidential VM. flattened_device_tree = {path = "rust-crates/flattened_device_tree"} -# Memoffset automatize calculating offsets to internal fields of Rust structures. These offsets are used then by +# Memoffset automatize calculating offsets to internal fields of Rust structures. These offsets are used then by # assembly code in the context switch implementation. memoffset = { version = "0.9", default-features = false, features = ["unstable_const"] } @@ -22,16 +22,19 @@ opensbi-sys = {path = "rust-crates/opensbi-sys"} pointers_utility = {path = "rust-crates/pointers_utility"} # This crate decodes RISC-V instructions unfortunately it does not support the C-instructions yet -riscv-decode = "0.2" +riscv-decode = "0.2" # The `spin` crate provides synchronization primitives (Mutexes etc) using spinlocks spin = {version="0.9", default-features = false, features = ["once", "rwlock", "spin_mutex"]} +# This crates provides functionality to parse TVM attestation payload (TAP). +riscv_cove_tap = {path = "rust-crates/riscv_cove_tap", features = ["parser"]} + # Used to measure confidential VM, required for attestation sha2 = { version = "0.10", default-features = false } # provides macros that help removing boilerplate code in the rust error handling -thiserror-no-std = "2.0" +thiserror-no-std = "2.0" [lib] name="ace" diff --git a/security-monitor/Makefile b/security-monitor/Makefile index 5aff0fb2..1acae82c 100644 --- a/security-monitor/Makefile +++ b/security-monitor/Makefile @@ -6,7 +6,7 @@ CARGO = cargo RELEASE = --release CHAIN=riscv64gc-unknown-none-elf TARGET = --target=$(CHAIN) -RUSTFLAGS = -Clink-arg=-Tsrc/platform/qemu/virt.lds -Crelocation-model=pie #-Ccode-model=medium +RUSTFLAGS = -Clink-arg=-Tsrc/platform/qemu/virt.lds -Crelocation-model=pie #-Ccode-model=medium EXEC_NAME = libace.a MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) MAKEFILE_SOURCE_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) @@ -20,9 +20,9 @@ OPENSBI_WORK_DIR=$(SM_WORK_DIR)/opensbi OPENSBI_SYS_WORK_DIR=$(SM_WORK_DIR)/opensbi_bindings OPENSBI_PATCH = $(PLATFORM_SOURCE_DIR)/opensbi_v1.4.patch -PLATFORM_RISCV_ABI ?= lp64d +PLATFORM_RISCV_ABI ?= lp64d PLATFORM_RISCV_ISA ?= rv64gc -PLATFORM_RISCV_XLEN ?= 64 +PLATFORM_RISCV_XLEN ?= 64 CROSS_COMPILE ?= riscv64-unknown-linux-gnu- all: audit opensbi_bindings build diff --git a/security-monitor/rust-crates/flattened_device_tree/src/error.rs b/security-monitor/rust-crates/flattened_device_tree/src/error.rs index e9c76622..6175cc6b 100644 --- a/security-monitor/rust-crates/flattened_device_tree/src/error.rs +++ b/security-monitor/rust-crates/flattened_device_tree/src/error.rs @@ -12,4 +12,8 @@ pub enum FdtError { FdtErrorParsing(#[from] DevTreeError), #[error("No memory node")] NoMemoryNode(), + #[error("Error when casting integers")] + FdtIntegerCasting(#[from] core::num::TryFromIntError), + #[error("Invalid integer value")] + FdtInvalidIntegerValue(), } \ No newline at end of file diff --git a/security-monitor/rust-crates/flattened_device_tree/src/lib.rs b/security-monitor/rust-crates/flattened_device_tree/src/lib.rs index 7046a103..a2bcd4a8 100644 --- a/security-monitor/rust-crates/flattened_device_tree/src/lib.rs +++ b/security-monitor/rust-crates/flattened_device_tree/src/lib.rs @@ -21,7 +21,7 @@ pub struct FlattenedDeviceTree<'a> { impl<'a> FlattenedDeviceTree<'a> { pub const FDT_HEADER_SIZE: usize = 40; - + /// Creates an instance of a wrapper over the library that parses the flattened device tree (FDT). Returns error if address is not a 8-bytes aligned pointer to the valid flattened device tree (FDT). /// /// # Safety @@ -69,6 +69,18 @@ impl<'a> FlattenedDeviceTree<'a> { Ok(FdtMemoryRegion { base: reg_prop.u64(0)?, size: reg_prop.u64(1)? }) } + + pub fn initrd(&self) -> Result<(usize, usize), FdtError> { + let initrd_start_prop = self.inner.props() + .find(|p| Ok(p.name()?.starts_with("linux,initrd-start")))? + .ok_or_else(|| FdtError::NoMemoryNode())?; + + let initrd_end_prop = self.inner.props() + .find(|p| Ok(p.name()?.starts_with("linux,initrd-end")))? + .ok_or_else(|| FdtError::NoMemoryNode())?; + + Ok((initrd_start_prop.u64(0)? as usize, initrd_end_prop.u64(0)? as usize )) + } } #[derive(Copy, Clone, Debug, Default)] @@ -77,6 +89,12 @@ pub struct FdtMemoryRegion { pub size: u64, } +impl FdtMemoryRegion { + pub fn into_range(&self) -> Result<(usize, usize), FdtError> { + Ok((usize::try_from(self.base)?, usize::try_from(self.base.checked_add(self.size).ok_or(FdtError::FdtInvalidIntegerValue())?)?)) + } +} + #[derive(Clone)] pub struct Hart<'a, 'dt> { inner: DevTreeNode<'a, 'dt>, diff --git a/security-monitor/rust-crates/opensbi.sh b/security-monitor/rust-crates/opensbi.sh index 85dc6708..9f6eb514 100755 --- a/security-monitor/rust-crates/opensbi.sh +++ b/security-monitor/rust-crates/opensbi.sh @@ -32,7 +32,7 @@ mkdir -p ${WORK_DIR} cd ${OPENSBI_SOURCE_DIR}/ -make CROSS_COMPILE=${CROSS_COMPILE} O=${WORK_DIR} PLATFORM=generic -j$(nproc) -make CROSS_COMPILE=${CROSS_COMPILE} I=${WORK_DIR} PLATFORM=generic install -j$(nproc) +make CROSS_COMPILE=${CROSS_COMPILE} O=${WORK_DIR} PLATFORM=generic -j$(nproc) +make CROSS_COMPILE=${CROSS_COMPILE} I=${WORK_DIR} PLATFORM=generic install -j$(nproc) echo "export PATH=${WORK_DIR}/lib:${WORK_DIR}/bin:\${PATH}" >> ${INSTALL_DIR}/env diff --git a/security-monitor/rust-crates/riscv_cove_tap/Cargo.toml b/security-monitor/rust-crates/riscv_cove_tap/Cargo.toml new file mode 100644 index 00000000..08154365 --- /dev/null +++ b/security-monitor/rust-crates/riscv_cove_tap/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "riscv_cove_tap" +version = "0.1.0" +authors = ["Wojciech Ozga "] +description = "Library to parse the RISC-V CoVE's TEE attestation payload" +edition = "2021" + +[dependencies] +#rsa = "0.9" # to create lockboxes: encrypt symetric key using public keys of target TEEs +#rand = "0.8" # to generate symmetric key used to encrypted payload + +# for symmetric encryption of payload +aes-gcm = {version="0.10.3", default-features = false, features=["aes", "alloc"]} + +# provides macros that help removing boilerplate code in rust error handling +thiserror-no-std = "2.0" + +[features] +parser = [] +serializer = [] \ No newline at end of file diff --git a/security-monitor/rust-crates/riscv_cove_tap/src/error.rs b/security-monitor/rust-crates/riscv_cove_tap/src/error.rs new file mode 100644 index 00000000..4b92ffd0 --- /dev/null +++ b/security-monitor/rust-crates/riscv_cove_tap/src/error.rs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +use thiserror_no_std::Error; + +#[derive(Error, Debug)] +pub enum TapError { + #[error("Unsupported TAP Lockbox algorithm {0}")] + UnsupportedLockboxAlgorithm(u16), + #[error("Unsupported TAP digest entry type {0}")] + UnsupportedDigestEntryType(u16), + #[error("Unsupported TAP digest algorithm {0}")] + UnsupportedDigestAlgorithm(u16), + #[error("Unsupported TAP payload encryption algorithm {0}")] + UnsupportedPayloadEncryptionAlgorithm(u16), + #[error("Invalid magic in the beginning of TAP")] + InvalidMagicStart(), + #[error("Invalid size of the TAP")] + InvalidSize(), + #[error("Aes error {0}")] + AesError(#[from] aes_gcm::Error) +} diff --git a/security-monitor/rust-crates/riscv_cove_tap/src/lib.rs b/security-monitor/rust-crates/riscv_cove_tap/src/lib.rs new file mode 100644 index 00000000..9d22d3e4 --- /dev/null +++ b/security-monitor/rust-crates/riscv_cove_tap/src/lib.rs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +#![no_std] +#![no_main] +extern crate alloc; + +mod error; +#[cfg(feature = "parser")] +mod parser; +#[cfg(feature = "serializer")] +mod serializer; + +#[cfg(feature = "parser")] +pub use parser::AttestationPayloadParser; + +#[cfg(feature = "serializer")] +pub use serializer::AttestationPayloadSerializer; + +pub use spec::*; +pub use error::*; +pub mod spec; diff --git a/security-monitor/rust-crates/riscv_cove_tap/src/parser.rs b/security-monitor/rust-crates/riscv_cove_tap/src/parser.rs new file mode 100644 index 00000000..39dbc993 --- /dev/null +++ b/security-monitor/rust-crates/riscv_cove_tap/src/parser.rs @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: 2023 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 + +pub use crate::error::TapError; +use alloc::vec::Vec; +use alloc::vec; +use crate::spec::*; + +pub struct AttestationPayloadParser { + pub pointer: *const u8, + pub size: usize, +} + +impl AttestationPayloadParser { + pub fn from_raw_pointer(pointer: *const u8, size: usize) -> Result { + Ok(Self { + pointer, size + }) + } + + pub fn parse_and_verify(&mut self) -> Result { + if self.read_u32()? != ACE_MAGIC_TAP_START { + return Err(TapError::InvalidMagicStart()); + } + self.read_u16()?; + // if self.read_u16()? as usize != self.size { + // return Err(TapError::InvalidSize()); + // } + let number_of_lockboxes = self.read_u16()?; + let mut lockboxes = vec![]; + for _ in 0..number_of_lockboxes { + let size = self.read_u16()? as usize; + let name = self.read_u64()?; + let algorithm = LockboxAlgorithm::from_u16(self.read_u16()?)?; + let value = self.read_exact(size-10)?; + lockboxes.push(Lockbox { + name, + algorithm, + value + }); + } + // TODO: recover symmetric key + let symmetric_key = [0u8; 32]; + + let payload_encryption_algorithm = PayloadEncryptionAlgorithm::from_u16(self.read_u16()?)?; + match payload_encryption_algorithm { + PayloadEncryptionAlgorithm::Debug => {}, + PayloadEncryptionAlgorithm::AesGcm256 => self.decrypt_aes_gcm_256(symmetric_key)?, + } + + let number_of_digests = self.read_u16()?; + let mut digests = vec![]; + for _ in 0..number_of_digests { + let size = self.read_u16()? as usize; + let pcr_id = self.read_u16()?; + let algorithm = DigestAlgorithm::from_u16(self.read_u16()?)?; + let value = self.read_exact(size-4)?; + digests.push(Digest { + pcr_id, + algorithm, + value + }); + } + + let number_of_secrets = self.read_u16()?; + let mut secrets = vec![]; + for _ in 0..number_of_secrets { + let size = self.read_u16()? as usize; + let name = self.read_u64()? as u64; + let value = self.read_exact(size-10)?; + secrets.push(Secret { name, value }); + } + + Ok(AttestationPayload { + lockboxes, + digests, + secrets, + }) + } + + fn decrypt_aes_gcm_256(&mut self, symmetric_key: [u8; 32]) -> Result<(), TapError> { + use aes_gcm::{AeadInPlace, Aes256Gcm, Key, KeyInit, Tag, Nonce}; + + let nonce_size = self.read_u16()? as usize; + let nonce = self.read_exact(nonce_size)?; + let tag_size = self.read_u16()? as usize; + let tag = self.read_exact(tag_size)?; + let payload_size = self.read_u16()? as usize; + + let key: Key = symmetric_key.into(); + let cipher = Aes256Gcm::new(&key); + let nonce = Nonce::from_slice(&nonce); + let tag = Tag::from_slice(&tag); + let mut data_slice = unsafe{ core::slice::from_raw_parts_mut(self.pointer as *mut u8, payload_size) }; + cipher.decrypt_in_place_detached(nonce, b"", &mut data_slice, &tag)?; + Ok(()) + } + + fn read_u16(&mut self) -> Result { + let value = unsafe { (self.pointer as *const u16).read_volatile() }; + self.pointer = self.pointer.wrapping_add(2); + Ok(value) + } + + fn read_u32(&mut self) -> Result { + let value = unsafe { (self.pointer as *const u32).read_volatile() }; + self.pointer = self.pointer.wrapping_add(4); + Ok(value) + } + + fn read_u64(&mut self) -> Result { + let value = unsafe { (self.pointer as *const u64).read_volatile() }; + self.pointer = self.pointer.wrapping_add(8); + Ok(value) + } + + fn read_exact(&mut self, size: usize) -> Result, TapError> { + let mut result = vec![]; + for _ in 0..size { + let value = unsafe { self.pointer.read_volatile() }; + self.pointer = self.pointer.wrapping_add(1); + result.push(value); + } + Ok(result) + } +} \ No newline at end of file diff --git a/security-monitor/rust-crates/riscv_cove_tap/src/serializer.rs b/security-monitor/rust-crates/riscv_cove_tap/src/serializer.rs new file mode 100644 index 00000000..43297efa --- /dev/null +++ b/security-monitor/rust-crates/riscv_cove_tap/src/serializer.rs @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2023 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +use crate::error::TapError; +use alloc::vec; +use crate::spec::*; +use alloc::vec::Vec; + +pub struct AttestationPayloadSerializer { + +} + +impl AttestationPayloadSerializer { + pub fn new() -> Self { + Self {} + } + + pub fn serialize(&self, mut payload: AttestationPayload) -> Result, TapError> { + let digests = self.serialize_digests(&mut payload)?; + let secrets = self.serialize_secrets(&mut payload)?; + let mut encrypted_part = self.encrypt_aes_gcm_256(digests, secrets)?; + let mut lockboxes = self.serialize_lockboxes(&mut payload)?; + + let total_size = lockboxes.len() + encrypted_part.len(); + + let mut result = vec![]; + result.append(&mut ACE_MAGIC_TAP_START.to_le_bytes().to_vec()); + result.append(&mut (total_size as u16).to_le_bytes().to_vec()); + result.append(&mut lockboxes); + result.append(&mut encrypted_part); + + Ok(result) + } + + fn serialize_lockboxes(&self, payload: &mut AttestationPayload) -> Result, TapError> { + // TODO: sanity check: lockboxes < 1024 + let mut result = vec![]; + result.append(&mut (payload.lockboxes.len() as u16).to_le_bytes().to_vec()); + for mut lockbox in payload.lockboxes.drain(..) { + let entry_size = lockbox.value.len() + 10; + result.append(&mut (entry_size as u16).to_le_bytes().to_vec()); + result.append(&mut (lockbox.name as u64).to_le_bytes().to_vec()); + result.append(&mut (lockbox.algorithm as u16).to_le_bytes().to_vec()); + result.append(&mut lockbox.value); + } + Ok(result) + } + + fn serialize_digests(&self, payload: &mut AttestationPayload) -> Result, TapError> { + // TODO: sanity check: digests < 1024 + let mut result = vec![]; + result.append(&mut (payload.digests.len() as u16).to_le_bytes().to_vec()); + for mut digest in payload.digests.drain(..) { + let entry_size = digest.value.len() + 2 + 2; + result.append(&mut (entry_size as u16).to_le_bytes().to_vec()); + result.append(&mut (digest.pcr_id).to_le_bytes().to_vec()); + result.append(&mut (digest.algorithm as u16).to_le_bytes().to_vec()); + result.append(&mut digest.value); + } + Ok(result) + } + + fn serialize_secrets(&self, payload: &mut AttestationPayload) -> Result, TapError> { + // TODO: sanity check: secrets < 1024 + let mut result = vec![]; + result.append(&mut (payload.secrets.len() as u16).to_le_bytes().to_vec()); + for mut secret in payload.secrets.drain(..) { + let entry_size = secret.value.len() + 10; + result.append(&mut (entry_size as u16).to_le_bytes().to_vec()); + result.append(&mut (secret.name).to_le_bytes().to_vec()); + result.append(&mut secret.value); + } + Ok(result) + } + + fn encrypt_aes_gcm_256(&self, mut digests: Vec, mut secrets: Vec) -> Result, TapError> { + use aes_gcm::{AeadInPlace, Aes256Gcm, Key, KeyInit}; + // use rand::rngs::OsRng; + + let mut encrypted_part = vec![]; + encrypted_part.append(&mut digests); + encrypted_part.append(&mut secrets); + + let symmetric_key = [0u8; 32]; + // rand::thread_rng().fill_bytes(&mut symmetric_key); + + let key: Key = symmetric_key.into(); + let cipher = Aes256Gcm::new(&key); + let nonce = [0u8; 12]; + // let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + let nonce = aes_gcm::Nonce::from_slice(&nonce); + let tag = cipher + .encrypt_in_place_detached(&nonce, b"", &mut encrypted_part) + .unwrap(); + + let mut result = vec![]; + result.append(&mut (PayloadEncryptionAlgorithm::AesGcm256 as u16).to_le_bytes().to_vec()); + result.append(&mut (nonce.as_slice().len() as u16).to_le_bytes().to_vec()); + result.append(&mut nonce.as_slice().to_vec()); + result.append(&mut (tag.as_slice().len() as u16).to_le_bytes().to_vec()); + result.append(&mut tag.as_slice().to_vec()); + result.append(&mut (encrypted_part.len() as u16).to_le_bytes().to_vec()); + result.append(&mut encrypted_part); + + Ok(result) + } + + // fn encrypt_rsa_2048_sha256_oasp(value: &[u8], public_key_file: String) -> Result, Error> { + // use rsa::pkcs1::DecodeRsaPublicKey; + // let public_key_pem: Vec = std::fs::read(public_key_file.clone()) + // .map_err(|_| Error::CannotOpenFile(public_key_file))?; + // let public_key = rsa::RsaPublicKey::from_pkcs1_pem(&String::from_utf8_lossy(&public_key_pem))?; + // let padding = rsa::Oaep::new::(); + // let encrypted_data = public_key.encrypt(&mut rand::thread_rng(), padding, value)?; + // Ok(encrypted_data) + // } +} \ No newline at end of file diff --git a/security-monitor/rust-crates/riscv_cove_tap/src/spec.rs b/security-monitor/rust-crates/riscv_cove_tap/src/spec.rs new file mode 100644 index 00000000..6538be04 --- /dev/null +++ b/security-monitor/rust-crates/riscv_cove_tap/src/spec.rs @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2023 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +use crate::error::TapError; +use alloc::vec::Vec; + +pub const ACE_HEADER_SIZE: usize = 8; +pub const ACE_FOOTER_SIZE: usize = 8; +pub const ACE_MAGIC_TAP_START: u32 = 0xACE0ACE0; +pub const ACE_MAGIC_TAP_END: u32 = 0xACE1ACE1; +pub const ACE_MAX_TAP_SIZE: usize = 4096; // size of the 4KiB page + +pub struct AttestationPayload { + pub lockboxes: Vec, + pub digests: Vec, + pub secrets: Vec, +} + +pub struct Lockbox { + pub name: u64, + pub algorithm: LockboxAlgorithm, + pub value: Vec, +} + +#[repr(u16)] +#[derive(Debug)] +pub enum LockboxAlgorithm { + Debug = 0, + Rsa2048Sha256Oasp = 1, +} + +impl LockboxAlgorithm { + pub fn from_u16(value: u16) -> Result { + match value { + 0 => Ok(Self::Debug), + 1 => Ok(Self::Rsa2048Sha256Oasp), + v => Err(TapError::UnsupportedLockboxAlgorithm(v)), + } + } +} + +pub struct Digest { + pub pcr_id: u16, + pub algorithm: DigestAlgorithm, + pub value: Vec, +} + +impl Digest { + pub fn value_in_hex(&self) -> alloc::string::String { + use crate::alloc::string::ToString; + self.value.iter().map(|b| alloc::format!("{:02x}", b).to_string()).collect::>().join("") + } + + pub fn pcr_id(&self) -> u16 { + self.pcr_id + } +} + +#[repr(u16)] +#[derive(Debug, PartialEq, Eq)] +pub enum DigestAlgorithm { + Debug = 0, + Sha512 = 1, +} + +impl DigestAlgorithm { + pub fn from_u16(value: u16) -> Result { + match value { + 0 => Ok(Self::Debug), + 1 => Ok(Self::Sha512), + v => Err(TapError::UnsupportedDigestAlgorithm(v)), + } + } + + pub fn digest_size(&self) -> u16 { + match self { + &Self::Debug => 0, + &Self::Sha512 => 512 / 8, + } + } +} + +pub struct Secret { + pub name: u64, + pub value: Vec, +} + +#[repr(u16)] +#[derive(Debug)] +pub enum PayloadEncryptionAlgorithm { + Debug = 0, + AesGcm256 = 1, +} + +impl PayloadEncryptionAlgorithm { + pub fn from_u16(value: u16) -> Result { + match value { + 0 => Ok(Self::Debug), + 1 => Ok(Self::AesGcm256), + v => Err(TapError::UnsupportedPayloadEncryptionAlgorithm(v)), + } + } +} \ No newline at end of file diff --git a/security-monitor/src/confidential_flow/finite_state_machine.rs b/security-monitor/src/confidential_flow/finite_state_machine.rs index 424243c3..e14f776d 100644 --- a/security-monitor/src/confidential_flow/finite_state_machine.rs +++ b/security-monitor/src/confidential_flow/finite_state_machine.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 IBM Corporation // SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich // SPDX-License-Identifier: Apache-2.0 +use crate::confidential_flow::handlers::attestation::RetrieveSecretRequest; use crate::confidential_flow::handlers::interrupts::{AllowExternalInterrupt, ExposeEnabledInterrupts, HandleInterrupt}; use crate::confidential_flow::handlers::mmio::{ AddMmioRegion, MmioLoadRequest, MmioLoadResponse, MmioStoreRequest, MmioStoreResponse, RemoveMmioRegion, @@ -92,6 +93,7 @@ impl<'a> ConfidentialFlow<'a> { VsEcall(Covg(AllowExternalInterrupt)) => AllowExternalInterrupt::from_confidential_hart(flow.confidential_hart()).handle(flow), VsEcall(Covg(ShareMemory)) => SharePageRequest::from_confidential_hart(flow.confidential_hart()).handle(flow), VsEcall(Covg(UnshareMemory)) => UnsharePageRequest::from_confidential_hart(flow.confidential_hart()).handle(flow), + VsEcall(Covg(RetrieveSecret)) => RetrieveSecretRequest::from_confidential_hart(flow.confidential_hart()).handle(flow), VsEcall(_) => InvalidCall::from_confidential_hart(flow.confidential_hart()).handle(flow), GuestLoadPageFault => MmioLoadRequest::from_confidential_hart(flow.confidential_hart()).handle(flow), VirtualInstruction => VirtualInstruction::from_confidential_hart(flow.confidential_hart()).handle(flow), diff --git a/security-monitor/src/confidential_flow/handlers/attestation/mod.rs b/security-monitor/src/confidential_flow/handlers/attestation/mod.rs new file mode 100644 index 00000000..6e79e83a --- /dev/null +++ b/security-monitor/src/confidential_flow/handlers/attestation/mod.rs @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: 2023 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +pub use retrieve_secret::RetrieveSecretRequest; + +mod retrieve_secret; diff --git a/security-monitor/src/confidential_flow/handlers/attestation/retrieve_secret.rs b/security-monitor/src/confidential_flow/handlers/attestation/retrieve_secret.rs new file mode 100644 index 00000000..78470abc --- /dev/null +++ b/security-monitor/src/confidential_flow/handlers/attestation/retrieve_secret.rs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2024 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +use crate::confidential_flow::handlers::sbi::SbiResponse; +use crate::confidential_flow::{ApplyToConfidentialHart, ConfidentialFlow}; +use crate::core::architecture::{GeneralPurposeRegister, PageSize}; +use crate::core::control_data::{ConfidentialHart, ControlDataStorage}; +use crate::core::memory_layout::ConfidentialVmPhysicalAddress; +use crate::error::Error; + +/// Provides the confidential VM with a secret that was decoded from the attestation payload during the promotion of the VM to confidential +/// VM. Secret is written to the buffer allocated by the confidential VM and passed as arguments to the call. +pub struct RetrieveSecretRequest { + output_buffer_address: ConfidentialVmPhysicalAddress, + output_buffer_size: usize, +} + +impl RetrieveSecretRequest { + pub fn from_confidential_hart(confidential_hart: &ConfidentialHart) -> Self { + Self { + output_buffer_address: ConfidentialVmPhysicalAddress::new(confidential_hart.gprs().read(GeneralPurposeRegister::a0)), + output_buffer_size: confidential_hart.gprs().read(GeneralPurposeRegister::a1), + } + } + + pub fn handle(self, confidential_flow: ConfidentialFlow) -> ! { + let transformation = ControlDataStorage::try_confidential_vm(confidential_flow.confidential_vm_id(), |ref mut confidential_vm| { + // ensure!(self.output_buffer_address.is_aligned_to(PageSize::Size4KiB.in_bytes()), Error::AddressNotAligned())?; + ensure!(self.output_buffer_size <= PageSize::Size4KiB.in_bytes(), Error::AddressNotAligned())?; + let secret = confidential_vm.secret(0)?; + ensure!(secret.len() <= self.output_buffer_size, Error::AddressNotAligned())?; + let mut buffer = [0u8; 8]; + for offset in 0..secret.len().div_ceil(8) { + let end_boundary = core::cmp::min(secret.len(), offset + 8); + (0..end_boundary).for_each(|i| buffer[i] = secret[offset + i]); + (end_boundary..8).for_each(|i| buffer[i] = 0u8); + let confidential_memory_address = + confidential_vm.memory_protector().translate_address(&self.output_buffer_address.add(offset))?; + unsafe { confidential_memory_address.write_volatile(usize::from_le_bytes(buffer)) }; + } + Ok(SbiResponse::success_with_code(secret.len())) + }) + .unwrap_or_else(|error| SbiResponse::error(error)); + confidential_flow.apply_and_exit_to_confidential_hart(ApplyToConfidentialHart::SbiResponse(transformation)) + } +} diff --git a/security-monitor/src/confidential_flow/handlers/mod.rs b/security-monitor/src/confidential_flow/handlers/mod.rs index a86368e4..78541f54 100644 --- a/security-monitor/src/confidential_flow/handlers/mod.rs +++ b/security-monitor/src/confidential_flow/handlers/mod.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 IBM Corporation // SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich // SPDX-License-Identifier: Apache-2.0 +pub mod attestation; pub mod interrupts; pub mod mmio; pub mod sbi; diff --git a/security-monitor/src/core/architecture/riscv/mmu/mod.rs b/security-monitor/src/core/architecture/riscv/mmu/mod.rs index 772883c5..a47f4877 100644 --- a/security-monitor/src/core/architecture/riscv/mmu/mod.rs +++ b/security-monitor/src/core/architecture/riscv/mmu/mod.rs @@ -5,6 +5,7 @@ use crate::core::architecture::CSR; use crate::core::memory_layout::NonConfidentialMemoryAddress; use crate::error::Error; +use crate::core::control_data::{ConfidentialVmMemoryLayout, MeasurementDigest}; pub use hgatp::{Hgatp, HgatpMode}; pub use page_size::PageSize; pub use page_table::PageTable; diff --git a/security-monitor/src/core/architecture/riscv/mmu/page_table.rs b/security-monitor/src/core/architecture/riscv/mmu/page_table.rs index 7dc35ddd..e3c156bc 100644 --- a/security-monitor/src/core/architecture/riscv/mmu/page_table.rs +++ b/security-monitor/src/core/architecture/riscv/mmu/page_table.rs @@ -7,7 +7,7 @@ use crate::core::architecture::mmu::page_table_level::PageTableLevel; use crate::core::architecture::mmu::paging_system::PagingSystem; use crate::core::architecture::mmu::HgatpMode; use crate::core::architecture::{PageSize, SharedPage}; -use crate::core::control_data::MeasurementDigest; +use crate::core::control_data::{ConfidentialVmMemoryLayout, MeasurementDigest, StaticMeasurements}; use crate::core::memory_layout::{ConfidentialMemoryAddress, ConfidentialVmPhysicalAddress, NonConfidentialMemoryAddress}; use crate::core::page_allocator::{Allocated, Page, PageAllocator}; use crate::error::Error; @@ -228,13 +228,25 @@ impl PageTable { /// Recursively extends measurements of all data pages in the order from the page with the lowest to the highest guest physical address. /// Returns error if the page table is malformed, i.e., there is a shared page mapping. - pub fn measure(&self, digest: &mut MeasurementDigest, address: usize) -> Result<(), Error> { + pub fn finalize( + &mut self, measurements: &mut StaticMeasurements, vm_memory_layout: &ConfidentialVmMemoryLayout, address: usize, + ) -> Result<(), Error> { use sha2::Digest; - self.logical_representation.iter().enumerate().try_for_each(|(i, entry)| { + self.logical_representation.iter_mut().enumerate().try_for_each(|(i, entry)| { let guest_physical_address = address + i * self.paging_system.data_page_size(self.level).in_bytes(); match entry { - LogicalPageTableEntry::PointerToNextPageTable(next_page_table) => next_page_table.measure(digest, guest_physical_address), - LogicalPageTableEntry::PageWithConfidentialVmData(page) => Ok(page.measure(digest, guest_physical_address)), + LogicalPageTableEntry::PointerToNextPageTable(next_page_table) => { + next_page_table.finalize(measurements, vm_memory_layout, guest_physical_address) + } + LogicalPageTableEntry::PageWithConfidentialVmData(page) => Ok(if vm_memory_layout.is_fdt(guest_physical_address) { + page.measure(measurements.pcr_fdt_mut(), guest_physical_address) + } else if vm_memory_layout.is_initrd(guest_physical_address) { + page.measure(measurements.pcr_initrd_mut(), guest_physical_address) + } else if vm_memory_layout.is_kernel(guest_physical_address) { + page.measure(measurements.pcr_kernel_mut(), guest_physical_address) + } else { + page.clear() + }), LogicalPageTableEntry::PageSharedWithHypervisor(_) => Err(Error::PageTableConfiguration()), LogicalPageTableEntry::NotMapped => Ok(()), } diff --git a/security-monitor/src/core/architecture/riscv/sbi/covg_extension.rs b/security-monitor/src/core/architecture/riscv/sbi/covg_extension.rs index b2590bcb..921aa98b 100644 --- a/security-monitor/src/core/architecture/riscv/sbi/covg_extension.rs +++ b/security-monitor/src/core/architecture/riscv/sbi/covg_extension.rs @@ -10,6 +10,7 @@ pub enum CovgExtension { UnshareMemory, AllowExternalInterrupt, DenyExternalInterrupt, + RetrieveSecret, Unknown(usize, usize), } @@ -21,6 +22,7 @@ impl CovgExtension { pub const SBI_EXT_COVG_UNSHARE_MEMORY: usize = 3; pub const SBI_EXT_COVG_ALLOW_EXT_INTERRUPT: usize = 4; pub const SBI_EXT_COVG_DENY_EXT_INTERRUPT: usize = 5; + pub const SBI_EXT_COVG_RETRIEVE_SECRET: usize = 9; pub fn from_function_id(function_id: usize) -> Self { match function_id { @@ -30,6 +32,7 @@ impl CovgExtension { Self::SBI_EXT_COVG_UNSHARE_MEMORY => Self::UnshareMemory, Self::SBI_EXT_COVG_ALLOW_EXT_INTERRUPT => Self::AllowExternalInterrupt, Self::SBI_EXT_COVG_DENY_EXT_INTERRUPT => Self::DenyExternalInterrupt, + Self::SBI_EXT_COVG_RETRIEVE_SECRET => Self::RetrieveSecret, _ => Self::Unknown(Self::EXTID, function_id), } } diff --git a/security-monitor/src/core/control_data/confidential_hart.rs b/security-monitor/src/core/control_data/confidential_hart.rs index d8d3e32f..83ea109d 100644 --- a/security-monitor/src/core/control_data/confidential_hart.rs +++ b/security-monitor/src/core/control_data/confidential_hart.rs @@ -130,6 +130,9 @@ impl ConfidentialHart { let mut confidential_hart = Self::from_vm_hart_reset(id, htimedelta, shared_memory); let confidential_hart_state = &mut confidential_hart.confidential_hart_state; confidential_hart_state.set_gprs(shared_memory.gprs()); + // this register is cleared as it can contain undeterministic address of TEE Attestation Payload that would destroy integrity + // measurements + confidential_hart_state.gprs_mut().write(GeneralPurposeRegister::a1, 0); confidential_hart_state.csrs_mut().vsstatus.save_nacl_value_in_main_memory(&shared_memory); confidential_hart_state.csrs_mut().vsie.save_nacl_value_in_main_memory(&shared_memory); confidential_hart_state.csrs_mut().vstvec.save_nacl_value_in_main_memory(&shared_memory); @@ -183,11 +186,9 @@ impl ConfidentialHart { } } - pub fn measure(&self) -> MeasurementDigest { - let mut measurement = MeasurementDigest::default(); - self.confidential_hart_state.gprs().measure(&mut measurement); - self.confidential_hart_state.csrs().measure(&mut measurement); - measurement + pub fn measure(&self, digest: &mut MeasurementDigest) { + self.confidential_hart_state.gprs().measure(digest); + self.confidential_hart_state.csrs().measure(digest); } pub fn address(&self) -> usize { diff --git a/security-monitor/src/core/control_data/confidential_vm.rs b/security-monitor/src/core/control_data/confidential_vm.rs index 40876ecb..113e7fd5 100644 --- a/security-monitor/src/core/control_data/confidential_vm.rs +++ b/security-monitor/src/core/control_data/confidential_vm.rs @@ -10,12 +10,14 @@ use crate::core::memory_protector::ConfidentialVmMemoryProtector; use crate::error::Error; use alloc::collections::BTreeMap; use alloc::vec::Vec; +use riscv_cove_tap::Secret; use spin::{Mutex, MutexGuard}; pub struct ConfidentialVm { id: ConfidentialVmId, - measurements: StaticMeasurements, confidential_harts: Vec, + _measurements: StaticMeasurements, + secrets: Vec, remote_commands: BTreeMap>>, memory_protector: ConfidentialVmMemoryProtector, allowed_external_interrupts: usize, @@ -37,7 +39,7 @@ impl ConfidentialVm { /// /// The id of the confidential VM must be unique. pub fn new( - id: ConfidentialVmId, mut confidential_harts: Vec, measurements: StaticMeasurements, + id: ConfidentialVmId, mut confidential_harts: Vec, _measurements: StaticMeasurements, secrets: Vec, mut memory_protector: ConfidentialVmMemoryProtector, ) -> Self { memory_protector.set_confidential_vm_id(id); @@ -50,10 +52,11 @@ impl ConfidentialVm { .collect(); Self { id, - measurements, confidential_harts, - memory_protector, + _measurements, + secrets, remote_commands, + memory_protector, allowed_external_interrupts: 0, mmio_regions: Vec::with_capacity(8), } @@ -63,10 +66,23 @@ impl ConfidentialVm { self.id } + pub fn memory_protector(&self) -> &ConfidentialVmMemoryProtector { + &self.memory_protector + } + pub fn memory_protector_mut(&mut self) -> &mut ConfidentialVmMemoryProtector { &mut self.memory_protector } + pub fn secret(&self, secret_id: usize) -> Result, Error> { + for secret in self.secrets.iter() { + if secret.name == secret_id as u64 { + return Ok(secret.value.to_vec()); + } + } + Err(Error::InvalidParameter()) + } + pub(super) fn deallocate(self) { self.memory_protector.into_root_page_table().deallocate(); } diff --git a/security-monitor/src/core/control_data/confidential_vm_measurement.rs b/security-monitor/src/core/control_data/confidential_vm_measurement.rs index 10d8e14b..6b038a4f 100644 --- a/security-monitor/src/core/control_data/confidential_vm_measurement.rs +++ b/security-monitor/src/core/control_data/confidential_vm_measurement.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 IBM Corporation // SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich // SPDX-License-Identifier: Apache-2.0 +use crate::error::Error; use sha2::digest::crypto_common::generic_array::GenericArray; pub type DigestType = sha2::Sha384; @@ -9,18 +10,40 @@ pub type MeasurementDigest = GenericArray Self { - let mut measurements = Self([MeasurementDigest::default(); NUMBER_OF_REGISTERS]); - measurements.0[TVM_CODE_AND_STATIC_DATA_REGISTER_ID] = measured_pages; - measurements.0[TVM_CONFIGURATION_REGISTER_ID] = configuration; - measurements + pub fn default() -> Self { + Self([MeasurementDigest::default(); NUMBER_OF_REGISTERS]) + } + + pub fn compare(&self, pcr_id: usize, digest: MeasurementDigest) -> Result { + ensure!(pcr_id < NUMBER_OF_REGISTERS, Error::InvalidParameter())?; + Ok(self.0[pcr_id] == digest) + } + + pub fn pcr_kernel_mut(&mut self) -> &mut MeasurementDigest { + &mut self.0[KERNEL_PCR_ID] + } + + pub fn pcr_initrd_mut(&mut self) -> &mut MeasurementDigest { + &mut self.0[INITRD_PCR_ID] + } + + pub fn pcr_fdt_mut(&mut self) -> &mut MeasurementDigest { + &mut self.0[FDT_PCR_ID] + } + + pub fn pcr_boot_hart_mut(&mut self) -> &mut MeasurementDigest { + &mut self.0[BOOT_HART_PCR_ID] } } diff --git a/security-monitor/src/core/control_data/confidential_vm_memory_layout.rs b/security-monitor/src/core/control_data/confidential_vm_memory_layout.rs new file mode 100644 index 00000000..9aca4770 --- /dev/null +++ b/security-monitor/src/core/control_data/confidential_vm_memory_layout.rs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 + +#[derive(Debug)] +pub struct ConfidentialVmMemoryLayout { + kernel: (usize, usize), + fdt: (usize, usize), + initrd: Option<(usize, usize)>, +} + +impl ConfidentialVmMemoryLayout { + pub fn new(kernel: (usize, usize), fdt: (usize, usize), initrd: Option<(usize, usize)>) -> Self { + Self { kernel, fdt, initrd } + } + + pub fn is_kernel(&self, address: usize) -> bool { + self.kernel.0 <= address && address < self.kernel.1 + } + + pub fn is_fdt(&self, address: usize) -> bool { + self.fdt.0 <= address && address < self.fdt.1 + } + + pub fn is_initrd(&self, address: usize) -> bool { + self.initrd.and_then(|o| Some(o.0 <= address && address < o.1)).unwrap_or(false) + } +} diff --git a/security-monitor/src/core/control_data/mod.rs b/security-monitor/src/core/control_data/mod.rs index f9a60fe5..432ba113 100644 --- a/security-monitor/src/core/control_data/mod.rs +++ b/security-monitor/src/core/control_data/mod.rs @@ -6,6 +6,7 @@ pub use confidential_hart_remote_command::{ConfidentialHartRemoteCommand, Confid pub use confidential_vm::ConfidentialVm; pub use confidential_vm_id::ConfidentialVmId; pub use confidential_vm_measurement::{DigestType, MeasurementDigest, StaticMeasurements}; +pub use confidential_vm_memory_layout::ConfidentialVmMemoryLayout; pub use confidential_vm_mmio_region::ConfidentialVmMmioRegion; pub use hardware_hart::{HardwareHart, HART_STACK_ADDRESS_OFFSET}; pub use hypervisor_hart::HypervisorHart; @@ -17,6 +18,7 @@ mod confidential_hart_remote_command; mod confidential_vm; mod confidential_vm_id; mod confidential_vm_measurement; +mod confidential_vm_memory_layout; mod confidential_vm_mmio_region; mod hardware_hart; mod hypervisor_hart; diff --git a/security-monitor/src/core/memory_layout/confidential_memory_address.rs b/security-monitor/src/core/memory_layout/confidential_memory_address.rs index 217f1ed8..bb8072f6 100644 --- a/security-monitor/src/core/memory_layout/confidential_memory_address.rs +++ b/security-monitor/src/core/memory_layout/confidential_memory_address.rs @@ -85,7 +85,7 @@ impl ConfidentialMemoryAddress { /// # Safety /// /// The caller must ensure that the address at given offset is still within the confidential memory region. - // TODO: can we require the offset to be a multiple of usize? + // TODO: can we require the offset to be a multiple of usize? (woz: yes we can) #[rr::only_spec] #[rr::params("l", "off", "lmax", "MEMORY_CONFIG")] #[rr::args("#l", "off", "lmax")] diff --git a/security-monitor/src/core/memory_layout/confidential_vm_physical_address.rs b/security-monitor/src/core/memory_layout/confidential_vm_physical_address.rs index a39b00ec..9f951d9e 100644 --- a/security-monitor/src/core/memory_layout/confidential_vm_physical_address.rs +++ b/security-monitor/src/core/memory_layout/confidential_vm_physical_address.rs @@ -17,10 +17,14 @@ impl ConfidentialVmPhysicalAddress { pub fn usize(&self) -> usize { self.0 } + + pub fn is_aligned_to(&self, align: usize) -> bool { + self.0 % align == 0 + } } impl core::fmt::Debug for ConfidentialVmPhysicalAddress { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "Confidential VM physical address={:x}", self.0) + write!(f, "0x{:x}", self.0) } } diff --git a/security-monitor/src/core/memory_protector/confidential_vm_memory_protector.rs b/security-monitor/src/core/memory_protector/confidential_vm_memory_protector.rs index 09cf5512..0968288a 100644 --- a/security-monitor/src/core/memory_protector/confidential_vm_memory_protector.rs +++ b/security-monitor/src/core/memory_protector/confidential_vm_memory_protector.rs @@ -4,7 +4,7 @@ use crate::core::architecture::mmu::{Hgatp, PageTable}; use crate::core::architecture::riscv::{mmu, pmp, tlb}; use crate::core::architecture::{PageSize, SharedPage}; -use crate::core::control_data::{ConfidentialVmId, MeasurementDigest}; +use crate::core::control_data::{ConfidentialVmId, ConfidentialVmMemoryLayout, StaticMeasurements}; use crate::core::memory_layout::{ConfidentialMemoryAddress, ConfidentialVmPhysicalAddress, NonConfidentialMemoryAddress}; use crate::error::Error; @@ -67,10 +67,11 @@ impl ConfidentialVmMemoryProtector { self.root_page_table.translate(address) } - pub fn measure(&self) -> Result { - let mut initial_digest = MeasurementDigest::default(); - self.root_page_table.measure(&mut initial_digest, 0)?; - Ok(initial_digest) + /// Cryptographically measures the confidential VM's memory. This function should be called during the confidential VM creation + /// procedure when the confidential VM's memory image creation has completed. These measurements will be used during attestation to + /// verify confidential VM's integrity and authenticity. + pub fn finalize(&mut self, measurements: &mut StaticMeasurements, vm_memory_layout: &ConfidentialVmMemoryLayout) -> Result<(), Error> { + Ok(self.root_page_table.finalize(measurements, vm_memory_layout, 0)?) } /// Reconfigures hardware to enable access initiated from this physical hart to memory regions owned by the diff --git a/security-monitor/src/core/page_allocator/page.rs b/security-monitor/src/core/page_allocator/page.rs index 6b22b387..b6396e2b 100644 --- a/security-monitor/src/core/page_allocator/page.rs +++ b/security-monitor/src/core/page_allocator/page.rs @@ -316,8 +316,10 @@ impl Page { // below unsafe is ok because the page has been initialized and it owns the entire memory region. // We are creating a slice of bytes, so the number of elements in the slice is the same as the size of the page. let slice: &[u8] = unsafe { core::slice::from_raw_parts(self.address().to_ptr(), self.size().in_bytes()) }; - hasher.update(&slice); - hasher.finalize_into(digest); + if slice.iter().find(|b| **b != 0).is_some() { + hasher.update(&slice); + hasher.finalize_into(digest); + } } /// Returns all usize-aligned offsets within the page. @@ -335,7 +337,7 @@ impl Page { #[rr::args("(#p, γ)")] /// Postcondition: The page has been zeroized. #[rr::observe("γ": "mk_page p.(page_loc) p.(page_sz) (zero_page p.(page_sz))")] - fn clear(&mut self) { + pub fn clear(&mut self) { // Safety: below unwrap() is fine because we iterate over page's offsets and thus always // request a write to an offset within the page. self.offsets().for_each( diff --git a/security-monitor/src/error.rs b/security-monitor/src/error.rs index 494788ae..ead5c078 100644 --- a/security-monitor/src/error.rs +++ b/security-monitor/src/error.rs @@ -35,14 +35,22 @@ pub enum Error { TooManyConfidentialVms(), #[error("Unsupported paging mode")] UnsupportedPagingMode(), + #[error("Incorrectly aligned FDT")] + FdtNotAlignedTo64Bits(), #[error("FDT size is invalid. Expecting at least 40 bytes and maximum 40960 bytes")] FdtInvalidSize(), #[error("Exceeded the max number of harts per VM")] InvalidNumberOfHartsInFdt(), - #[error("Incorrectly aligned authentication blob")] - AuthBlobNotAlignedTo64Bits(), - #[error("Authentication blob size is invalid.")] + #[error("Incorrectly aligned authentication payload")] + AuthBlobNotAlignedTo32Bits(), + #[error("Authentication payload size is invalid.")] AuthBlobInvalidSize(), + #[error("Error when parsing TEE attestation payload: {0}")] + AttestationPayloadParsingError(#[from] riscv_cove_tap::TapError), + #[error("Local attestation failed. Invalid measurements")] + LocalAttestationFailed(), + #[error("Local attestation failed. Not supported digest type")] + LocalAttestationNotSupportedDigest(), /* SBI invalid address */ #[error("Address is not aligned")] @@ -122,9 +130,10 @@ impl Error { Self::InvalidCall(_, _) => SBI_ERR_INVALID_PARAM as usize, Self::PageTableCorrupted() => SBI_ERR_INVALID_PARAM as usize, Self::UnsupportedPagingMode() => SBI_ERR_INVALID_PARAM as usize, + Self::FdtNotAlignedTo64Bits() => SBI_ERR_INVALID_PARAM as usize, Self::FdtInvalidSize() => SBI_ERR_INVALID_PARAM as usize, Self::InvalidNumberOfHartsInFdt() => SBI_ERR_INVALID_PARAM as usize, - Self::AuthBlobNotAlignedTo64Bits() => SBI_ERR_INVALID_PARAM as usize, + Self::AuthBlobNotAlignedTo32Bits() => SBI_ERR_INVALID_PARAM as usize, Self::AuthBlobInvalidSize() => SBI_ERR_INVALID_PARAM as usize, Self::DeviceTreeError(_) => SBI_ERR_INVALID_PARAM as usize, diff --git a/security-monitor/src/non_confidential_flow/handlers/cove_host_extension/promote_to_confidential_vm.rs b/security-monitor/src/non_confidential_flow/handlers/cove_host_extension/promote_to_confidential_vm.rs index 9a3f0700..21bccf66 100644 --- a/security-monitor/src/non_confidential_flow/handlers/cove_host_extension/promote_to_confidential_vm.rs +++ b/security-monitor/src/non_confidential_flow/handlers/cove_host_extension/promote_to_confidential_vm.rs @@ -4,7 +4,7 @@ use crate::core::architecture::riscv::sbi::NaclSharedMemory; use crate::core::architecture::{GeneralPurposeRegister, Hgatp, PageSize}; use crate::core::control_data::{ - ConfidentialHart, ConfidentialVm, ConfidentialVmId, ControlDataStorage, HypervisorHart, StaticMeasurements, + ConfidentialHart, ConfidentialVm, ConfidentialVmId, ConfidentialVmMemoryLayout, ControlDataStorage, HypervisorHart, StaticMeasurements, }; use crate::core::memory_layout::ConfidentialVmPhysicalAddress; use crate::core::memory_protector::ConfidentialVmMemoryProtector; @@ -14,6 +14,7 @@ use crate::non_confidential_flow::handlers::supervisor_binary_interface::SbiResp use crate::non_confidential_flow::{ApplyToHypervisorHart, NonConfidentialFlow}; use alloc::vec::Vec; use flattened_device_tree::FlattenedDeviceTree; +use riscv_cove_tap::{AttestationPayload, AttestationPayloadParser, Secret}; /// Creates a confidential VM in a single-step. This handler implements the Promote to TVM call defined by the COVH ABI in the CoVE /// specification. With this call, the hypervisor presents a state of a virtual machine, requesting the security monitor to promote it to a @@ -27,25 +28,25 @@ use flattened_device_tree::FlattenedDeviceTree; /// * The address of the authentication blob must be either `0` or aligned to 8 bytes. pub struct PromoteToConfidentialVm { fdt_address: ConfidentialVmPhysicalAddress, - auth_blob_address: Option, + attestation_payload_address: Option, program_counter: usize, hgatp: Hgatp, } impl PromoteToConfidentialVm { const FDT_ALIGNMENT_IN_BYTES: usize = 8; - const TAP_ALIGNMENT_IN_BYTES: usize = 8; + const TAP_ALIGNMENT_IN_BYTES: usize = 4; const BOOT_HART_ID: usize = 0; pub fn from_hypervisor_hart(hypervisor_hart: &HypervisorHart) -> Self { let fdt_address = ConfidentialVmPhysicalAddress::new(hypervisor_hart.gprs().read(GeneralPurposeRegister::a0)); - let auth_blob_address = match hypervisor_hart.gprs().read(GeneralPurposeRegister::a1) { + let attestation_payload_address = match hypervisor_hart.gprs().read(GeneralPurposeRegister::a1) { 0 => None, address => Some(ConfidentialVmPhysicalAddress::new(address)), }; let program_counter = hypervisor_hart.gprs().read(GeneralPurposeRegister::a2); let hgatp = Hgatp::from(hypervisor_hart.csrs().hgatp.read()); - Self { fdt_address, auth_blob_address, program_counter, hgatp } + Self { fdt_address, attestation_payload_address, program_counter, hgatp } } pub fn handle(self, non_confidential_flow: NonConfidentialFlow) -> ! { @@ -63,13 +64,13 @@ impl PromoteToConfidentialVm { } fn create_confidential_vm(&self, shared_memory: &NaclSharedMemory) -> Result { - debug!("Promoting a VM into a confidential VM"); - // Copy the entire VM's state to the confidential memory, recreating the MMU configuration. - let memory_protector = ConfidentialVmMemoryProtector::from_vm_state(&self.hgatp)?; + debug!("Promoting a VM to a confidential VM"); + // Copying the entire VM's state to the confidential memory, recreating the MMU configuration + let mut memory_protector = ConfidentialVmMemoryProtector::from_vm_state(&self.hgatp)?; // The pointer to the flattened device tree (FDT) as well as the entire FDT must be treated as an untrusted input, which measurement // is reflected during attestation. We can parse FDT only after moving VM's data (and the FDT) to the confidential memory. - let number_of_confidential_harts = self.process_device_tree(&memory_protector)?; + let (vm_memory_layout, number_of_confidential_harts) = self.process_device_tree(&memory_protector)?; // TODO: generate htimedelta let htimedelta = 0; @@ -82,86 +83,134 @@ impl PromoteToConfidentialVm { }) .collect(); - let measured_pages_digest = memory_protector.measure()?; - let confidential_hart_digest = confidential_harts[Self::BOOT_HART_ID].measure(); - let measurements = StaticMeasurements::new(measured_pages_digest, confidential_hart_digest); - debug!("VM measurements: {:?}", measurements); + let attestation_payload = + self.read_attestation_payload(&memory_protector).inspect_err(|e| debug!("TAP reading failed: {:?}", e)).unwrap_or(None); + let measurements = self.measure(&mut memory_protector, &vm_memory_layout, &confidential_harts)?; - self.authenticate_and_authorize_vm(&memory_protector, &measurements)?; + let secrets = self + .authenticate_and_authorize_vm(attestation_payload, &measurements) + .inspect_err(|e| debug!("Local attestation failed: {:?}", e)) + .unwrap_or(alloc::vec![]); ControlDataStorage::try_write(|control_data| { // We have a write lock on the entire control data! Spend here as little time as possible because we are // blocking all other harts from accessing the control data. This influences all confidential VMs in the system! let id = control_data.unique_id()?; - control_data.insert_confidential_vm(ConfidentialVm::new(id, confidential_harts, measurements, memory_protector)) + control_data.insert_confidential_vm(ConfidentialVm::new(id, confidential_harts, measurements, secrets, memory_protector)) }) } - fn process_device_tree(&self, memory_protector: &ConfidentialVmMemoryProtector) -> Result { + fn measure( + &self, memory_protector: &mut ConfidentialVmMemoryProtector, vm_memory_layout: &ConfidentialVmMemoryLayout, + confidential_harts: &Vec, + ) -> Result { + let mut measurements = StaticMeasurements::default(); + memory_protector.finalize(&mut measurements, vm_memory_layout)?; + confidential_harts[Self::BOOT_HART_ID].measure(measurements.pcr_boot_hart_mut()); + debug!("VM measurements: {:?}", measurements); + Ok(measurements) + } + + fn process_device_tree(&self, memory_protector: &ConfidentialVmMemoryProtector) -> Result<(ConfidentialVmMemoryLayout, usize), Error> { + debug!("Reading flatten device tree (FDT) at memory address {:?}", self.fdt_address); let address_in_confidential_memory = memory_protector.translate_address(&self.fdt_address)?; // Make sure that the address is 8-bytes aligned. Once we ensure this, we can safely read 8 bytes because they must be within // the page boundary. These 8 bytes should contain the `magic` (first 4 bytes) and `size` (next 4 bytes). - ensure!(address_in_confidential_memory.is_aligned_to(Self::FDT_ALIGNMENT_IN_BYTES), Error::AuthBlobNotAlignedTo64Bits())?; + ensure!(address_in_confidential_memory.is_aligned_to(Self::FDT_ALIGNMENT_IN_BYTES), Error::FdtNotAlignedTo64Bits())?; // Below use of unsafe is ok because (1) the security monitor owns the memory region containing the data of the not-yet-created // confidential VM's and (2) there is only one physical hart executing this code. let fdt_total_size = unsafe { FlattenedDeviceTree::total_size(address_in_confidential_memory.to_ptr())? }; ensure!(fdt_total_size >= FlattenedDeviceTree::FDT_HEADER_SIZE, Error::FdtInvalidSize())?; - // To work with FDT, we must have it as a continous chunk of memory. We accept only FDTs that fit within 2MiB + // To work with FDT, we must have it as a continous chunk of memory. We accept only FDTs that fit within 2 MiB ensure!(fdt_total_size.div_ceil(PageSize::Size2MiB.in_bytes()) == 1, Error::FdtInvalidSize())?; - let large_page = Self::relocate(memory_protector, &self.fdt_address, fdt_total_size)?; + let large_page = Self::relocate(memory_protector, &self.fdt_address, fdt_total_size, false)?; // Security note: We parse untrusted FDT using an external library. A vulnerability in this library might blow up our security - // guarantees! Below unsafe is ok because FDT address is at least size of the FDT header and all FDT is in a continuous chunk of - // memory. See the safety requirements of `FlattenedDeviceTree::from_raw_pointer`. - let number_of_confidential_harts = match unsafe { FlattenedDeviceTree::from_raw_pointer(large_page.address().to_ptr()) } { - Ok(device_tree) => device_tree.harts().count(), - Err(_) => 0, - }; + // guarantees! Below unsafe is ok because the FDT address aligned to (at least) the size of the FDT header and all FDT is in a + // continuous chunk of memory. See the safety requirements of `FlattenedDeviceTree::from_raw_pointer`. + let device_tree = unsafe { FlattenedDeviceTree::from_raw_pointer(large_page.address().to_ptr()) }?; + + let number_of_confidential_harts = device_tree.harts().count(); + let kernel = device_tree.memory().and_then(|r| r.into_range())?; + let initrd = device_tree.initrd().ok(); + + let vm_memory_layout = + ConfidentialVmMemoryLayout::new(kernel, (self.fdt_address.usize(), self.fdt_address.usize() + fdt_total_size), initrd); + debug!("Virtual machine's memory layout: {:?}", vm_memory_layout); // Clean up, deallocate pages PageAllocator::release_pages(alloc::vec![large_page.deallocate()]); ensure!(number_of_confidential_harts > 0, Error::InvalidNumberOfHartsInFdt())?; ensure!(number_of_confidential_harts < ConfidentialVm::MAX_NUMBER_OF_HARTS_PER_VM, Error::InvalidNumberOfHartsInFdt())?; - Ok(number_of_confidential_harts) + + Ok((vm_memory_layout, number_of_confidential_harts)) + } + + fn read_attestation_payload(&self, memory_protector: &ConfidentialVmMemoryProtector) -> Result, Error> { + match self.attestation_payload_address { + Some(attestation_payload_address) => { + debug!("Reading TEE attestation payload (TAP) at memory address {:?}", attestation_payload_address); + let address_in_confidential_memory = memory_protector.translate_address(&attestation_payload_address)?; + // Make sure that the address is 8-bytes aligned. Once we ensure this, we can safely read 8 bytes because they must be + // within the page boundary. These 8 bytes should contain the `magic` (first 4 bytes) and `size` (next 2 + // bytes). + ensure!(address_in_confidential_memory.is_aligned_to(Self::TAP_ALIGNMENT_IN_BYTES), Error::AuthBlobNotAlignedTo32Bits())?; + // Below use of unsafe is ok because (1) the security monitor owns the memory region containing the data of the + // not-yet-created confidential VM's and (2) there is only one physical hart executing this code. + let header: u64 = + unsafe { address_in_confidential_memory.read_volatile().try_into().map_err(|_| Error::AuthBlobNotAlignedTo32Bits())? }; + let total_size = riscv_cove_tap::ACE_HEADER_SIZE + ((header >> 32) & 0xFFFF) as usize; + // To work with the authentication blob, we must have it as a continous chunk of memory. We accept only authentication blobs + // that fit within 2MiB + ensure!(total_size.div_ceil(PageSize::Size2MiB.in_bytes()) == 1, Error::AuthBlobInvalidSize())?; + let large_page = Self::relocate(memory_protector, &attestation_payload_address, total_size, true)?; + // TODO: we should parse to the blob key that will allow to unlock the lockbox. + let mut parser = unsafe { AttestationPayloadParser::from_raw_pointer(large_page.address().to_ptr(), total_size)? }; + let attestation_payload = parser.parse_and_verify()?; + // Clean up, deallocate pages + PageAllocator::release_pages(alloc::vec![large_page.deallocate()]); + Ok(Some(attestation_payload)) + } + None => Ok(None), + } } /// Performs local attestation. It decides if the VM can be promote into a confidential VM and decrypts the attestation secret intended /// for this confidential VM. fn authenticate_and_authorize_vm( - &self, memory_protector: &ConfidentialVmMemoryProtector, _measurements: &StaticMeasurements, - ) -> Result<(), Error> { - if let Some(blob_address) = self.auth_blob_address { - debug!("Performing local attestation"); - let address_in_confidential_memory = memory_protector.translate_address(&blob_address)?; - // Make sure that the address is 8-bytes aligned. Once we ensure this, we can safely read 8 bytes because they must be within - // the page boundary. These 8 bytes should contain the `magic` (first 4 bytes) and `size` (next 4 bytes). - ensure!(address_in_confidential_memory.is_aligned_to(Self::TAP_ALIGNMENT_IN_BYTES), Error::AuthBlobNotAlignedTo64Bits())?; - // Below use of unsafe is ok because (1) the security monitor owns the memory region containing the data of the not-yet-created - // confidential VM's and (2) there is only one physical hart executing this code. - let magic_and_size: usize = unsafe { address_in_confidential_memory.read_volatile() }; - let auth_blob_total_size: usize = u32::from_be((magic_and_size >> 32) as u32) as usize; - // To work with the authentication blob, we must have it as a continous chunk of memory. We accept only authentication blobs - // that fit within 2MiB - ensure!(auth_blob_total_size.div_ceil(PageSize::Size2MiB.in_bytes()) == 1, Error::AuthBlobInvalidSize())?; - let large_page = Self::relocate(memory_protector, &blob_address, auth_blob_total_size)?; - - // TODO: local attestation should occure here, i.e., we should verify the authentication blob signature - // TODO: compare measurements to the one signed in the authentication blob - - // Clean up, deallocate pages - PageAllocator::release_pages(alloc::vec![large_page.deallocate()]); + &self, attestation_payload: Option, measurements: &StaticMeasurements, + ) -> Result, Error> { + use crate::core::control_data::MeasurementDigest; + match attestation_payload { + Some(attestation_payload) => { + ensure!(attestation_payload.digests.len() > 0, Error::LocalAttestationFailed())?; + for digest in attestation_payload.digests.iter() { + debug!("Reference PCR{:?}={:?}=0x{}", digest.pcr_id, digest.algorithm, digest.value_in_hex()); + ensure!(digest.algorithm == riscv_cove_tap::DigestAlgorithm::Sha512, Error::LocalAttestationNotSupportedDigest())?; + let pcr_value = MeasurementDigest::clone_from_slice(&digest.value); + ensure!(measurements.compare(digest.pcr_id() as usize, pcr_value)?, Error::LocalAttestationFailed())?; + } + debug!("Attestation succeeded, fetched {} secrets", attestation_payload.secrets.len()); + Ok(attestation_payload.secrets) + } + None => Ok(alloc::vec![]), } - Ok(()) } - /// Copies a buffer into a single large page. The input buffer is continuous across guest physical pages with G-stage address + /// Copies a buffer into a single large page. + /// + /// Why do we need this function? The input buffer is continuous across guest physical pages with G-stage address /// translation enabled but might not be continuous across the real physical pages. The output buffer is continous accross real /// physical pages. Returns error if (1) the buffer to copy is larger than 2 MiB page, or (2) the base address is not aligned to - /// 8-bytes. The caller is responsible for deallocating the page. + /// 8-bytes. + /// + /// Safety: + /// * The caller of this function is responsible for deallocating the page returned from this function. fn relocate( memory_protector: &ConfidentialVmMemoryProtector, base_address: &ConfidentialVmPhysicalAddress, number_of_bytes_to_copy: usize, + clear: bool, ) -> Result, Error> { ensure!((base_address.usize() as *const u8).is_aligned_to(core::mem::size_of::()), Error::AddressNotAligned())?; let mut large_page = PageAllocator::acquire_page(PageSize::Size2MiB)?.zeroize(); @@ -171,6 +220,9 @@ impl PromoteToConfidentialVm { let confidential_vm_physical_address = base_address.add(copied_bytes); let confidential_memory_address = memory_protector.translate_address(&confidential_vm_physical_address)?; let value: usize = unsafe { confidential_memory_address.read_volatile() }; + if clear { + unsafe { confidential_memory_address.write_volatile(0) }; + } large_page.write(copied_bytes, value)?; copied_bytes += core::mem::size_of::(); } diff --git a/tools/cove_tap_tool/Cargo.toml b/tools/cove_tap_tool/Cargo.toml new file mode 100644 index 00000000..07a76e12 --- /dev/null +++ b/tools/cove_tap_tool/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "cove-tap-tool" +version = "0.1.0" +authors = ["Wojciech Ozga "] +description = "Tool to create TVM attestation payload for CoVE's confidential VM" +edition = "2021" + +[dependencies] +clap = { version="4", features = ["derive"] } # for command line argument parsing + +byteorder = "1.5" # to serialize numbers using little/big endianness +sha2 = "0.10" # to calculate integrity measurements of kernel, initramfs etc. +hmac = "0.12" # to calculate HMAC over encrypted payload +rsa = "0.9" # to create lockboxes: encrypt symetric key using public keys of target TEEs +rand = "0.8" # to generate symmetric key used to encrypted payload +aes-gcm = "0.10.3" # for symmetric encryption of payload + +riscv_cove_tap = {path="../../security-monitor/rust-crates/riscv_cove_tap", features=["parser", "serializer"]} + +thiserror = "1.0" # provides macros that help removing boilerplate code in the rust error handling diff --git a/tools/cove_tap_tool/Makefile b/tools/cove_tap_tool/Makefile new file mode 100644 index 00000000..3d011ced --- /dev/null +++ b/tools/cove_tap_tool/Makefile @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2023 IBM Corporation +# SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +# SPDX-License-Identifier: Apache-2.0 +CARGO = cargo +RELEASE = --release +CHAIN=riscv64gc-unknown-linux-gnu +TARGET = --target=$(CHAIN) +RUSTFLAGS = -C linker=riscv64-unknown-linux-gnu-gcc +EXEC_NAME = cove-tap-tool +MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +MAKEFILE_SOURCE_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) + +ACE_DIR := $(if $(ACE_DIR),$(ACE_DIR),$(MAKEFILE_SOURCE_DIR)../build/target/) + +WORK_DIR=$(ACE_DIR)/cove_tap_tool +TOOLS_DIR=$(ACE_DIR)/tools + +PLATFORM_RISCV_ABI ?= lp64d +PLATFORM_RISCV_ISA ?= rv64gc +PLATFORM_RISCV_XLEN ?= 64 +CROSS_COMPILE ?= riscv64-unknown-linux-gnu- + +all: build + +build: fmt + echo "Building Local Attestation utility" ;\ + mkdir -p $(WORK_DIR) ; \ + rm -f $(WORK_DIR)/$(CHAIN)/release/$(EXEC_NAME); \ + RUSTFLAGS='$(RUSTFLAGS)' CARGO_TARGET_DIR=$(WORK_DIR) INSTALL_DIR=$(ACE_DIR)/ $(CARGO) build $(RELEASE) $(TARGET) ; \ + cp $(WORK_DIR)/$(CHAIN)/release/$(EXEC_NAME) $(TOOLS_DIR)/ ; + +doc: + @$(CARGO) doc + +check: build test + +audit: + @$(CARGO) audit + +clippy: + @$(CARGO) clippy + +fmt: + @$(CARGO) fmt + +bench: + @$(CARGO) bench + +clean: + @$(CARGO) clean + rm -f bin/$(EXEC_NAME) + +.PHONY: all build hack opensbi doc check test bench clean diff --git a/tools/cove_tap_tool/README.md b/tools/cove_tap_tool/README.md new file mode 100644 index 00000000..d25a57c7 --- /dev/null +++ b/tools/cove_tap_tool/README.md @@ -0,0 +1,3 @@ +# Usage + +Generate symmetric key \ No newline at end of file diff --git a/tools/cove_tap_tool/src/attach.rs b/tools/cove_tap_tool/src/attach.rs new file mode 100644 index 00000000..85e0078b --- /dev/null +++ b/tools/cove_tap_tool/src/attach.rs @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2024 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +use crate::error::Error; +use byteorder::WriteBytesExt; +use std::fs::OpenOptions; +use std::io::BufReader; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; + +pub fn attach_tap( + input_file: String, + tap_file_name: String, + output_file: Option, +) -> Result<(), Error> { + let output_file_name = match output_file { + Some(f) if input_file != f => { + std::fs::copy(input_file, f.clone())?; + f + } + Some(f) => f, + None => input_file, + }; + let offset = find_placehoder(&output_file_name)?; + // clear the placeholder + let mut output_file = OpenOptions::new().write(true).open(output_file_name)?; + output_file.seek(SeekFrom::Start(offset))?; + (riscv_cove_tap::ACE_HEADER_SIZE..riscv_cove_tap::ACE_MAX_TAP_SIZE).try_for_each(|_| output_file.write_u8(0u8))?; + // write expected TAP from the beginning of the offset + output_file.seek(SeekFrom::Start(offset))?; + let mut tap_file = OpenOptions::new().read(true).open(tap_file_name)?; + std::io::copy(&mut tap_file, &mut output_file)?; + println!("TAP attached successfully"); + Ok(()) +} + +fn find_placehoder(output_file_name: &str) -> Result { + let output_file = OpenOptions::new().read(true).open(output_file_name)?; + let mut buf = BufReader::new(output_file); + let mut buffer = [0u8; 4]; + let mut offset = 0u64; + while let Ok(bytes_read) = buf.read(&mut buffer) { + if bytes_read == 0 { + break; + } + if u32::from_le_bytes(buffer) == riscv_cove_tap::ACE_MAGIC_TAP_START { + println!("Found TAP placeholder at offset: {:x}", offset); + return Ok(offset); + } + offset += bytes_read as u64; + } + Err(Error::PlaceholderError()) +} diff --git a/tools/cove_tap_tool/src/error.rs b/tools/cove_tap_tool/src/error.rs new file mode 100644 index 00000000..35bb72f9 --- /dev/null +++ b/tools/cove_tap_tool/src/error.rs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("IO Error")] + IoError(#[from] std::io::Error), + #[error("PKCS1 Error")] + Pkcs1Error(#[from] rsa::pkcs1::Error), + #[error("RSA Error")] + RsaError(#[from] rsa::Error), + #[error("Invalid parameter")] + InvalidParameter(String), + #[error("Tap Error")] + TapSerializerError(riscv_cove_tap::TapError), + #[error("Int casting Error")] + TryFromIntErr(#[from] std::num::TryFromIntError), + #[error("Cannot open file {0}")] + CannotOpenFile(String), + #[error("Could not parse int")] + IntParseError(#[from] core::num::ParseIntError), + #[error("Placeholder error")] + PlaceholderError(), +} + +impl From for Error { + fn from(err: riscv_cove_tap::TapError) -> Self { + Error::TapSerializerError(err) + } +} + +#[macro_export] +macro_rules! ensure { + ($cond:expr, $error:expr) => { + if !$cond { + Err($error) + } else { + Ok(()) + } + }; +} diff --git a/tools/cove_tap_tool/src/generate.rs b/tools/cove_tap_tool/src/generate.rs new file mode 100644 index 00000000..c8d3fd7f --- /dev/null +++ b/tools/cove_tap_tool/src/generate.rs @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2024 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +use crate::ensure; +use crate::error::Error; +use std::fs::OpenOptions; +use std::io::Write; +use riscv_cove_tap::AttestationPayload; +use riscv_cove_tap::AttestationPayloadSerializer; +use riscv_cove_tap::Digest; +use riscv_cove_tap::DigestAlgorithm; +use riscv_cove_tap::Lockbox; +use riscv_cove_tap::LockboxAlgorithm; +use riscv_cove_tap::Secret; + +pub fn generate_tap( + pcrs: Vec<(u16, Vec)>, + confidential_vm_secrets: Vec<(u64, Vec)>, + tee_public_keys_files: Vec, + output_file: String, +) -> Result<(), Error> { + ensure!( + confidential_vm_secrets.len() < 256, + Error::InvalidParameter(format!("Confidential VM can receive maximum 256 secrets")) + )?; + ensure!( + tee_public_keys_files.len() < 1024, + Error::InvalidParameter(format!("Confidential VM TAP supports max 1024 lockboxes")) + )?; + + // let symmetric_key = [0u8; 32]; + + let mut lockboxes = vec![]; + lockboxes.push(Lockbox { + name: 0u64, + algorithm: LockboxAlgorithm::Debug, + value: [0xFF; 16].to_vec(), + }); + + let mut digests = vec![]; + for (pcr_id, pcr_value) in pcrs.into_iter() { + let tap_digest = Digest { + pcr_id, + algorithm: DigestAlgorithm::Sha512, + value: pcr_value, + }; + println!("Writing PCR{}={}", pcr_id, tap_digest.value_in_hex()); + digests.push(tap_digest); + } + + let mut secrets = vec![]; + for (secret_name, secret_value) in confidential_vm_secrets.into_iter() { + let secret = Secret { + name: secret_name, + value: secret_value, + }; + println!("Writing secret {}", secret_name); + secrets.push(secret); + } + + let tap = AttestationPayload { + lockboxes, + digests, + secrets, + }; + + let serializer = AttestationPayloadSerializer::new(); + let serialized = serializer.serialize(tap)?; + + // write the entire TAP to the output file + let mut output = OpenOptions::new() + .create_new(true) + .read(true) + .write(true) + .append(false) + .open(output_file.clone()) + .map_err(|_| Error::CannotOpenFile(output_file.clone()))?; + output.write(&serialized)?; + + Ok(()) +} diff --git a/tools/cove_tap_tool/src/main.rs b/tools/cove_tap_tool/src/main.rs new file mode 100644 index 00000000..a99731bf --- /dev/null +++ b/tools/cove_tap_tool/src/main.rs @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2024 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +use crate::error::Error; +use clap::{Parser, Subcommand}; + +mod attach; +mod error; +mod generate; +mod measure; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Args { + #[command(subcommand)] + cmd: Commands, +} + +#[derive(Subcommand, Debug, Clone)] +enum Commands { + Attach { + #[arg(short, long)] + input_file: String, + #[arg(short, long)] + tap_file: String, + #[arg(short, long)] + output_file: Option, + }, + Generate { + #[arg(short='p', long="pcrs", value_parser = parse_key_val::, value_delimiter = ',', required=true)] + pcrs: Vec<(u16, Vec)>, + #[arg(long="secrets", value_parser = parse_key_val::, value_delimiter = ',')] + confidential_vm_secrets: Vec<(u64, Vec)>, + #[clap(short, long, value_delimiter = ' ', num_args = 1..)] + tee_public_keys_files: Vec, + #[arg(short, long)] + output_file: String, + }, + Measure { + #[arg(short, long)] + kernel_file: String, + }, +} + +/// Parse a single key-value pair +fn parse_key_val( + s: &str, +) -> Result<(T, Vec), Box> +where + T: std::str::FromStr, + T::Err: std::error::Error + Send + Sync + 'static, + U: std::str::FromStr, + U::Err: std::error::Error + Send + Sync + 'static, +{ + let pos = s + .find('=') + .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?; + let key = s[..pos].parse()?; + let value = &s[pos + 1..]; + let value = if value.starts_with("0x") { + decode_hex(&value[2..])? + } else { + value.as_bytes().to_vec() + }; + Ok((key, value)) +} + +pub fn decode_hex(s: &str) -> Result, core::num::ParseIntError> { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) + .collect() +} + +fn main() -> Result<(), Error> { + Ok(match Args::parse().cmd { + Commands::Attach { + input_file, + tap_file, + output_file, + } => attach::attach_tap(input_file, tap_file, output_file), + Commands::Generate { + pcrs, + confidential_vm_secrets, + tee_public_keys_files, + output_file, + } => generate::generate_tap( + pcrs, + confidential_vm_secrets, + tee_public_keys_files, + output_file, + ), + Commands::Measure { kernel_file } => measure::measure(kernel_file), + }?) +} diff --git a/tools/cove_tap_tool/src/measure.rs b/tools/cove_tap_tool/src/measure.rs new file mode 100644 index 00000000..c3470c26 --- /dev/null +++ b/tools/cove_tap_tool/src/measure.rs @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2024 IBM Corporation +// SPDX-FileContributor: Wojciech Ozga , IBM Research - Zurich +// SPDX-License-Identifier: Apache-2.0 +use crate::error::Error; +use std::fs::OpenOptions; + +use sha2::digest::crypto_common::generic_array::GenericArray; +pub type DigestType = sha2::Sha384; +pub type MeasurementDigest = + GenericArray::OutputSize>; + +pub fn measure(kernel_file: String) -> Result<(), Error> { + use std::io::BufReader; + use std::io::Read; + + let mut digest = MeasurementDigest::default(); + + let kernel = OpenOptions::new().read(true).open(kernel_file)?; + let mut buf = BufReader::new(kernel); + let mut buffer = [0u8; 4096]; // 1 4KiB page + let mut address = 0x80000000 as u64; + while let Ok(bytes_read) = buf.read(&mut buffer) { + if bytes_read == 0 { + break; + } + let header = [buffer[0], buffer[1], buffer[2], buffer[3]]; + if u32::from_le_bytes(header) == riscv_cove_tap::ACE_MAGIC_TAP_START { + (0..4096).for_each(|i| buffer[i] = 0); // security monitor will clear it + } + if buffer.iter().find(|b| **b != 0).is_some() { + use sha2::Digest; + let mut hasher = DigestType::new_with_prefix(digest.clone()); + hasher.update(address.to_le_bytes()); + hasher.update(&buffer); + hasher.finalize_into(&mut digest); + // println!("Page 0x{:x} {:100x}", address, digest); + } else { + // println!("Page 0x{:x} empty", address); + } + address += 4096; + (0..4096).for_each(|i| buffer[i] = 0); + } + println!("Digest 0x{:100x}", digest); + Ok(()) +}