Message ID | 20190417103938.7762-1-jarkko.sakkinen@linux.intel.com (mailing list archive) |
---|---|
Headers | show |
Series | Intel SGX1 support | expand |
On Wed, Apr 17, 2019 at 01:39:10PM +0300, Jarkko Sakkinen wrote: Good morning, I hope this note finds the impending end of the work week going well for everyone. First of all, a thank you to Jarkko for his efforts in advancing this driver, a process that can be decidedly thankless. Our apologies in advance for contributing to this phenomenon. > Intel(R) SGX is a set of CPU instructions that can be used by > applications to set aside private regions of code and data. The code > outside the enclave is disallowed to access the memory inside the > enclave by the CPU access control. In a way you can think that SGX > provides inverted sandbox. It protects the application from a > malicious host. Unfortunately, in its current implementation, the driver will not protect a host from malicious enclaves. If it advances in its current form, the mainline Linux kernel will be implementing a driver with known and documented security issues. In addition, the driver breaks all existing SGX software by breaking compatibility with what is a 3+ year ABI provided by the existing driver. This seems to contravene the well understood philosophy that Linux doesn't, if at all possible, break existing applications, something that we believe can be easily prevented if those interested in these issues choose to read on. The following reflections and code come from a team that has been active in SGX development, since before hardware was even publically available. A team that has authored a complete re-implementation of all of the SGX runtime infrastructure, which is the only consumer of the services provided by an SGX driver. A team that has also authored and maintains a significant cohort of SGX based applications. So I believe we speak from a vantage point of experience with respect to these issues and not from a 'bike-shedding' or political perspective. The fundamental problem is that the proposed driver lacks an enclave initialization policy mechanism consistent with the security guarantees that SGX hardware is designed to deliver. SGX is designed to provide system architects a framework for building IAGO resistent security architectures through the use of cryptographically defined identities. Other then an attempt to limit acccess to the PROVISION attribute, the driver makes no attempt to regulate, in a cryptographically secure fashion, what enclaves can be initialized, who can initialize them or what characteristics they can be initialized with. The security implications of this were recently documented in a paper by researchers at Graz, here is the link for those interested: https://arxiv.org/pdf/1702.08719.pdf Both the current controls for enclave access to the PROVISION attribute and the security controls that are being proposed to emerge for the driver, sometime in the future, suffer from being dependent on discretionary access controls, ie. file privileges, that can be defeated by a privilege escalation attack. Those of us building architectures on top of this technology have a need to certify that an application will provide security contracts robust in the face of a privilege escalation event or platform compromise. To be very blunt, and my apologies for doing so, the design for this driver has been driven by idealogy rather then by technology. Our focus with this e-mail and proposed driver modification is an attempt to satisfy both technical requirements and political idealogy, if that is even possible. When we raised these issues six months ago, we were told that we were handwaving bloat into the kernel. We take a criticism like that very seriously so we are returning with code, in what we believe is 'bloat-free' form. The attached patch, against the jarkko-sgx/master branch of Jarkko's driver repository, provides a framework that implements cryptographically secure enclave initialization policies. The patch and it's signature are also available from the following URL's: ftp://ftp.idfusion.net/pub/idfusion/jarkko-master-SFLC.patch ftp://ftp.idfusion.net/pub/idfusion/jarkko-master-SFLC.patch.asc The modification, in series form, is also available in the following GIT repository: git://git.idfusion.net/src/linux-sgx/in-tree jarkko-master-SFLC The driver modification implements cryptographically secure enclave initialization only if the platform owner chooses to do so and does not limit the development of alternative strategies in the future. It also allows the platform owner to choose the ability to remain compatible with existing runtimes and applications. The policy architecture allows just about any conceivable initialization policy to be implemented and in any combination. It supports both a plurality of launch enclaves as well as support for multiple signing keys if a token-less approach is desired. It also provides cryptographically secure control of access to the PROVISION attribute and the ability to completely block access to the attribute if that is desired. The driver modification has been extensively tested in all of these scenarios, including combinations thereof. We just completed a major round of development that was based on Flexible Launch Control platforms and deployed onto fixed launch control platforms using the out-of-tree driver, without a need to maintain disparate runtimes. On that note, given that the new driver API is completely incompatible with all of the existing runtime software in the field, we believe the issue of proper testing of this driver is an open problem. I'm assuming that Intel has a yet to be released PSW/SDK that supports the driver, otherwise our enhancements to the driver is the only way that it can experience legitimate field testing. The interface for using the policy management framework is straight forward. The following three pseudo-files are available: /sys/kernel/security/sgx/launch_keys /sys/kernel/security/sgx/provisioning_keys /sys/kernel/security/sgx/signing_keys The SHA256 hashes of the cryptographic keys used to sign enclaves (MRSIGNER values) are written into these files, ie, the following command: echo "SHA256_HASH" >| /sys/kernel/security/sgx/signing_keys Would only allow enclaves to be initialized whose MRSIGNER value matches the SHA256_HASH value. The 'launch_keys' file maintains a list of signer signatures that are allowed to initialize enclaves with the EINITTOKEN attribute set. The 'provisioning_keys' file maintains a list of signer signatures that are allowed to initialize enclaves with the PROVISION attribute set. Writing the 'lock' keyword to a file permanently blocks any further directives from being recognized. At that point, a very targeted ring-0 attack would need to be conducted to circumvent the enclave initialization policies. This provides a framework that allows a flexible launch control platform to attain the approximate security guarantees offered by a fixed launch control platform. If the platform owner chooses to do nothing, the driver will initialize any enclave that is presented to it. FWIW, we have close to a century of enterprise system's administration experience on our team and we could not envision any competent system's administrator, concerned about security, who would allow such a configuration. So that is how it all works, the changelogs in the GIT repository have much more extensive documentation. We fit the entire architecture into approximately one page of memory, which seems to be a minor amount of bloat for everyone getting to do whatever they want to do, including leaving Linux mainline with a secure driver implementation. Here is the diffstat: --------------------------------------------------------------------------- arch/x86/include/uapi/asm/sgx.h | 2 + arch/x86/kernel/cpu/sgx/driver/Makefile | 1 + arch/x86/kernel/cpu/sgx/driver/driver.h | 7 + arch/x86/kernel/cpu/sgx/driver/ioctl.c | 88 ++++- arch/x86/kernel/cpu/sgx/driver/main.c | 5 + arch/x86/kernel/cpu/sgx/driver/policy.c | 584 ++++++++++++++++++++++++++++++++ arch/x86/kernel/cpu/sgx/main.c | 27 +- arch/x86/kernel/cpu/sgx/sgx.h | 3 +- 8 files changed, 704 insertions(+), 13 deletions(-) --------------------------------------------------------------------------- With respect to a mainline Linux driver at large, an issue that we believe needs clarification is whether or not a mandate will be issued by Intel to OEM's that Flexible Launch Control is mandatory on all platforms that are henceforth shipped. Otherwise the mainline Linux driver will be in the unenviable position of only working sporadically and will not be a universal solution for all hardware. We have a solution for that as well, but the above is probably enough cannon fodder for debate so we will leave that sleeping dog lie for the time being. Hopefully all of the above is helpful for enhancing the quality of the Linux SGX eco-system. Best wishes for a productive weekend. Dr. Greg As always, Dr. G.W. Wettstein, Ph.D. Enjellic Systems Development, LLC. 4206 N. 19th Ave. Specializing in information infra-structure Fargo, ND 58102 development. PH: 701-281-1686 FAX: 701-281-3949 EMAIL: greg@enjellic.com ------------------------------------------------------------------------------ "I can only provide the information, I can't make you hear it." -- Shelley Bainter arch/x86/include/uapi/asm/sgx.h | 2 + arch/x86/kernel/cpu/sgx/driver/Makefile | 1 + arch/x86/kernel/cpu/sgx/driver/driver.h | 7 + arch/x86/kernel/cpu/sgx/driver/ioctl.c | 88 ++++- arch/x86/kernel/cpu/sgx/driver/main.c | 5 + arch/x86/kernel/cpu/sgx/driver/policy.c | 584 ++++++++++++++++++++++++++++++++ arch/x86/kernel/cpu/sgx/main.c | 27 +- arch/x86/kernel/cpu/sgx/sgx.h | 3 +- 8 files changed, 704 insertions(+), 13 deletions(-) diff --git a/arch/x86/include/uapi/asm/sgx.h b/arch/x86/include/uapi/asm/sgx.h index 9ed690a38c70..694981e03c65 100644 --- a/arch/x86/include/uapi/asm/sgx.h +++ b/arch/x86/include/uapi/asm/sgx.h @@ -53,7 +53,9 @@ struct sgx_enclave_add_page { * @sigstruct: address for the SIGSTRUCT data */ struct sgx_enclave_init { + __u64 addr; __u64 sigstruct; + __u64 einittoken; }; /** diff --git a/arch/x86/kernel/cpu/sgx/driver/Makefile b/arch/x86/kernel/cpu/sgx/driver/Makefile index 01ebbbb06a47..ac7702201229 100644 --- a/arch/x86/kernel/cpu/sgx/driver/Makefile +++ b/arch/x86/kernel/cpu/sgx/driver/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_INTEL_SGX_DRIVER) += sgx.o sgx-$(CONFIG_INTEL_SGX_DRIVER) += ioctl.o sgx-$(CONFIG_INTEL_SGX_DRIVER) += main.o +sgx-$(CONFIG_INTEL_SGX_DRIVER) += policy.o diff --git a/arch/x86/kernel/cpu/sgx/driver/driver.h b/arch/x86/kernel/cpu/sgx/driver/driver.h index 153b4a48aa6f..d5994c4aa65d 100644 --- a/arch/x86/kernel/cpu/sgx/driver/driver.h +++ b/arch/x86/kernel/cpu/sgx/driver/driver.h @@ -33,6 +33,13 @@ extern u32 sgx_xsave_size_tbl[64]; extern const struct file_operations sgx_provision_fops; +uint8_t * sgx_get_launch_signer(uint8_t *signature); +int sgx_get_key_hash(const void *modulus, void *hash); +bool sgx_launch_control(uint8_t *modulus); +bool sgx_provisioning_control(uint8_t *modulus); +bool sgx_signing_control(uint8_t *modulus); +int sgx_policy_fs(void); +void sgx_policy_fs_remove(void); long sgx_ioctl(struct file *filep, unsigned int cmd, unsigned long arg); #endif /* __ARCH_X86_INTEL_SGX_H__ */ diff --git a/arch/x86/kernel/cpu/sgx/driver/ioctl.c b/arch/x86/kernel/cpu/sgx/driver/ioctl.c index 3a01c3dd579d..0d758f1498f7 100644 --- a/arch/x86/kernel/cpu/sgx/driver/ioctl.c +++ b/arch/x86/kernel/cpu/sgx/driver/ioctl.c @@ -639,7 +639,17 @@ static int __sgx_get_key_hash(struct crypto_shash *tfm, const void *modulus, return crypto_shash_digest(shash, modulus, SGX_MODULUS_SIZE, hash); } -static int sgx_get_key_hash(const void *modulus, void *hash) +/** +* sgx_get_key_hash - Compute the hash of the enclave signer. +* +* @modulus: A pointer to the signature modulus. +* @hash: A pointer to the signature buffer. +* +* Return: +* 0 on success, +* Cryptographic subsystem error. +*/ +int sgx_get_key_hash(const void *modulus, void *hash) { struct crypto_shash *tfm; int ret; @@ -655,20 +665,26 @@ static int sgx_get_key_hash(const void *modulus, void *hash) } static int sgx_encl_init(struct sgx_encl *encl, struct sgx_sigstruct *sigstruct, - struct sgx_einittoken *token) + struct sgx_einittoken *token, bool use_lc) { u64 mrsigner[4]; int ret; int i; int j; + uint8_t *(*get_signer)(uint8_t *) = NULL; /* Check that the required attributes have been authorized. */ if (encl->secs_attributes & ~encl->allowed_attributes) return -EINVAL; - ret = sgx_get_key_hash(sigstruct->modulus, mrsigner); - if (ret) - return ret; + if (use_lc) + get_signer = sgx_get_launch_signer; + else { + pr_debug("%s: Using modulus signature.\n", __FILE__); + ret = sgx_get_key_hash(sigstruct->modulus, mrsigner); + if (ret) + return ret; + } flush_work(&encl->work); @@ -683,7 +699,7 @@ static int sgx_encl_init(struct sgx_encl *encl, struct sgx_sigstruct *sigstruct, for (i = 0; i < SGX_EINIT_SLEEP_COUNT; i++) { for (j = 0; j < SGX_EINIT_SPIN_COUNT; j++) { ret = sgx_einit(sigstruct, token, encl->secs.epc_page, - mrsigner); + mrsigner, get_signer); if (ret == SGX_UNMASKED_EVENT) continue; else @@ -737,6 +753,7 @@ static int sgx_encl_init(struct sgx_encl *encl, struct sgx_sigstruct *sigstruct, static long sgx_ioc_enclave_init(struct file *filep, unsigned int cmd, unsigned long arg) { + bool use_sc, use_lc, use_pc; struct sgx_enclave_init *initp = (struct sgx_enclave_init *)arg; struct sgx_encl *encl = filep->private_data; struct sgx_einittoken *einittoken; @@ -744,6 +761,25 @@ static long sgx_ioc_enclave_init(struct file *filep, unsigned int cmd, struct page *initp_page; int ret; + use_sc = sgx_signing_control(NULL); + pr_debug("%s: Signing control status: %s\n", __FILE__, + use_sc ? "active" : "disabled"); + + use_lc = sgx_launch_control(NULL); + pr_debug("%s: Launch control status: %s\n", __FILE__, + use_lc ? "active" : "disabled"); + + use_pc = sgx_provisioning_control(NULL); + pr_debug("%s: Provisioning control status: %s\n", __FILE__, + use_pc ? "active" : "disabled"); + + if (!use_sc) { + if (initp->einittoken != 0 && !use_lc) + return -EINVAL; + if (initp->einittoken == 0 && use_lc) + return -EINVAL; + } + initp_page = alloc_page(GFP_HIGHUSER); if (!initp_page) return -ENOMEM; @@ -751,7 +787,16 @@ static long sgx_ioc_enclave_init(struct file *filep, unsigned int cmd, sigstruct = kmap(initp_page); einittoken = (struct sgx_einittoken *) ((unsigned long)sigstruct + PAGE_SIZE / 2); - memset(einittoken, 0, sizeof(*einittoken)); + if ( !initp->einittoken ) + memset(einittoken, 0, sizeof(*einittoken)); + else { + if (copy_from_user(einittoken, + (void __user *)initp->einittoken, + sizeof(*einittoken))) { + ret = -EFAULT; + goto out; + } + } if (copy_from_user(sigstruct, (void __user *)initp->sigstruct, sizeof(*sigstruct))) { @@ -759,8 +804,35 @@ static long sgx_ioc_enclave_init(struct file *filep, unsigned int cmd, goto out; } + if (!use_lc && (sigstruct->body.attributes & SGX_ATTR_EINITTOKENKEY)) { + ret = -EINVAL; + goto out; + } + + if ((sigstruct->body.attributes & SGX_ATTR_EINITTOKENKEY) && + sgx_launch_control(sigstruct->modulus)) + encl->allowed_attributes |= SGX_ATTR_EINITTOKENKEY; + + if (!use_pc) + encl->allowed_attributes |= SGX_ATTR_PROVISIONKEY; + else { + if ((sigstruct->body.attributes & SGX_ATTR_PROVISIONKEY) && + sgx_provisioning_control(sigstruct->modulus)) + encl->allowed_attributes |= SGX_ATTR_PROVISIONKEY; + } + + if (use_sc) { + use_sc = sgx_signing_control(sigstruct->modulus); + if (!use_sc && !use_lc) { + ret = -EINVAL; + goto out; + } + if (use_sc) + use_lc = false; + } + + ret = sgx_encl_init(encl, sigstruct, einittoken, use_lc); - ret = sgx_encl_init(encl, sigstruct, einittoken); out: kunmap(initp_page); diff --git a/arch/x86/kernel/cpu/sgx/driver/main.c b/arch/x86/kernel/cpu/sgx/driver/main.c index afe844aa81d6..503c2a9728ec 100644 --- a/arch/x86/kernel/cpu/sgx/driver/main.c +++ b/arch/x86/kernel/cpu/sgx/driver/main.c @@ -348,6 +348,10 @@ static int __init sgx_drv_init(void) { int ret; + ret = sgx_policy_fs(); + if (ret) + return ret; + ret = sgx_drv_subsys_init(); if (ret) return ret; @@ -362,6 +366,7 @@ module_init(sgx_drv_init); static void __exit sgx_drv_exit(void) { + sgx_policy_fs_remove(); platform_driver_unregister(&sgx_drv); sgx_drv_subsys_exit(); } diff --git a/arch/x86/kernel/cpu/sgx/driver/policy.c b/arch/x86/kernel/cpu/sgx/driver/policy.c new file mode 100644 index 000000000000..c72b1604c1fa --- /dev/null +++ b/arch/x86/kernel/cpu/sgx/driver/policy.c @@ -0,0 +1,584 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) IDfusion, LLC + +#define KEY_SIZE 32 + +#include <linux/types.h> +#include <linux/seq_file.h> +#include <linux/atomic.h> +#include <linux/security.h> +#include "driver.h" + +/* Variables for launch key management. */ +struct list_key { + struct list_head list; + uint8_t key[KEY_SIZE]; +}; + +static struct dentry *sgx_fs; +static struct dentry *launch_keys; +static atomic_t launch_keys_opencount = ATOMIC_INIT(1); +static unsigned int launch_keys_count; +static bool launch_keys_locked; +static DEFINE_MUTEX(launch_key_list_mutex); +static LIST_HEAD(launch_key_list); + +static struct dentry *provision_keys; +static atomic_t provision_keys_opencount = ATOMIC_INIT(1); +static unsigned int provision_keys_count; +static bool provision_keys_locked; +static DEFINE_MUTEX(provision_key_list_mutex); +static LIST_HEAD(provision_key_list); + +static struct dentry *signing_keys; +static atomic_t signing_keys_opencount = ATOMIC_INIT(1); +static unsigned int signing_keys_count; +static bool signing_keys_locked; +static DEFINE_MUTEX(signing_key_list_mutex); +static LIST_HEAD(signing_key_list); + +/** + * have_signer - Verify the presence presence of a key signer. + * + * @signature: Pointer to signature of signer. + * + * Return: + * 0 Signer signature was not found. + * 1 Signer signature was found. + */ +static bool have_signer(struct list_head *keylist, struct mutex *lock, + uint8_t *signature) +{ + bool retn = false; + struct list_key *kp; + + mutex_lock(lock); + list_for_each_entry(kp, keylist, list) { + pr_debug("%s: Checking signer=%*phN, ks=%*phN\n", __func__, + KEY_SIZE, signature, KEY_SIZE, kp->key); + if (memcmp(kp->key, signature, KEY_SIZE) == 0) { + retn = true; + goto done; + } + } + + done: + mutex_unlock(lock); + return retn; +} + +/** + * sgx_launch_control - Launch Control policy status + * + * Verifies whether or not launch control is active. + * + * Return: + * 0 No launch control is authorized. + * 1 Launch control is permitted. + */ +bool sgx_launch_control(uint8_t *modulus) +{ + bool retn = false; + uint8_t mrsigner[KEY_SIZE]; + + if (modulus == NULL) { + retn = launch_keys_count > 0; + goto done; + } + + pr_debug("%s: Verifying launch control modulus.\n", __func__); + if (sgx_get_key_hash(modulus, mrsigner)) + goto done; + retn = have_signer(&launch_key_list, &launch_key_list_mutex, mrsigner); + + done: + return retn; +} + +/** + * sgx_get_launch_signer - Iterate through list of enclave authorizers. + * + * @signer: The last returned enclave signer. + * + * This function iterates through the list of enclave signers from the + * last signature. Calling the function with a NULL signer value + * resets the iteration to the beginning of the list. + * + * Return: + * NULL indicates end of list + * non-NULL the next enclave signature on the list. + */ + +uint8_t * sgx_get_launch_signer(uint8_t *signature) +{ + bool seeking = false; + uint8_t *retn = NULL; + struct list_key *kp; + + if (!signature) { + kp = list_first_entry(&launch_key_list, struct list_key, list); + return kp->key; + } + + kp = list_last_entry(&launch_key_list, struct list_key, list); + if ( memcmp(kp->key, signature, sizeof(kp->key)) == 0 ) + return NULL; + + mutex_lock(&launch_key_list_mutex); + list_for_each_entry(kp, &launch_key_list, list) { + if (seeking) { + retn = kp->key; + goto done; + } + pr_debug("%s: Skipping: %*phN\n", __func__, KEY_SIZE, kp->key); + if (memcmp(kp->key, signature, KEY_SIZE) == 0) + seeking = true; + } + + done: + mutex_unlock(&launch_key_list_mutex); + return retn; +} + +/** + * sgx_provisioning_control - Provisioning control verification. + * + * This function returns a true value if provision control is active + * and the signature of the modulus of the signing key for an enclave + * with the PROVISION bit set is on the list of approved keys. + * + * Return: + * 0 No provisioning control is authorized. + * 1 Provisioning is authorized for the enclave. + */ +bool sgx_provisioning_control(uint8_t *modulus) +{ + bool retn = false; + uint8_t mrsigner[KEY_SIZE]; + + if (modulus == NULL) { + retn = provision_keys_count > 0; + goto done; + } + + pr_debug("Verifying provisioning control signature.\n"); + if (sgx_get_key_hash(modulus, mrsigner)) + goto done; + retn = have_signer(&provision_key_list, &provision_key_list_mutex, + mrsigner); + + done: + return retn; +} + +/** + * sgx_signing_control - Signing control verification. + * + * This function returns a true value if signing control is active + * and the signature of the modulus of the signing key for an enclave + * is on the list of approved keys. + * + * Return: + * 0 No signing control is authorized. + * 1 Signing is authorized for the enclave. + */ +bool sgx_signing_control(uint8_t *modulus) +{ + bool retn = false; + uint8_t mrsigner[KEY_SIZE]; + + if (modulus == NULL) { + retn = signing_keys_count > 0; + goto done; + } + + pr_debug("Verifying signing control signature.\n"); + if (sgx_get_key_hash(modulus, mrsigner)) + goto done; + retn = have_signer(&signing_key_list, &signing_key_list_mutex, + mrsigner); + + done: + return retn; +} + +static int process_write_key(const char __user *buf, size_t datalen, + unsigned int *keycnt, struct mutex *lock, + struct list_head *keylist) +{ + ssize_t retn; + + char *p, keybufr[KEY_SIZE*2 + 1], key[KEY_SIZE]; + + struct list_key *kp; + + if (datalen != sizeof(keybufr)) { + retn = -EINVAL; + goto done; + } + + memset(keybufr, '\0', sizeof(keybufr)); + if (copy_from_user(keybufr, buf, datalen)) { + retn = -EFAULT; + goto done; + } + + p = strchr(keybufr, '\n'); + if (!p) { + retn = -EINVAL; + goto done; + } + *p = '\0'; + if (hex2bin(key, keybufr, sizeof(key))) { + retn = -EINVAL; + goto done; + } + + kp = kzalloc(sizeof(*kp), GFP_KERNEL); + if (!kp) { + retn = -ENOMEM; + goto done; + } + memcpy(kp->key, key, sizeof(kp->key)); + + mutex_lock(lock); + list_add_tail(&kp->list, keylist); + ++*keycnt; + mutex_unlock(lock); + + retn = datalen; + pr_debug("%s: Added key: %*phN\n", __func__, KEY_SIZE, key); + + done: + return retn; +} + +static int process_lock(const char __user *buf, size_t datalen, bool *lockfile) +{ + char bufr[5]; + + if (datalen != strlen("lock") + 1) + return 0; + + memset(bufr, '\0', sizeof(bufr)); + if (copy_from_user(bufr, buf, datalen-1)) + return -EFAULT; + if ( strcmp(bufr, "lock") != 0 ) + return 0; + + *lockfile = true; + return datalen; +} + +static int process_clear(const char __user *buf, size_t datalen, char *type, + unsigned int *keycnt, struct mutex *lock, + struct list_head *keylist) +{ + char bufr[6]; + struct list_key *kp, *kp_tmp; + + if (datalen != strlen("clear") + 1) + return 0; + + memset(bufr, '\0', sizeof(bufr)); + if (copy_from_user(bufr, buf, datalen-1)) + return -EFAULT; + if ( strcmp(bufr, "clear") != 0 ) + return 0; + + mutex_lock(lock); + list_for_each_entry_safe(kp, kp_tmp, keylist, list) { + pr_debug("[%s]: Freeing signature: %*phN\n", __FILE__, + KEY_SIZE, kp->key); + list_del(&kp->list); + kfree(kp); + } + *keycnt = 0; + mutex_unlock(lock); + + pr_info("Cleared %s signatures.\n", type); + return datalen; +} + +static int process_dump(const char __user *buf, size_t datalen, char *type, + struct mutex *lock, struct list_head *keylist) +{ + char bufr[5]; + struct list_key *kp; + + if (datalen != strlen("dump") + 1) + return 0; + + memset(bufr, '\0', sizeof(bufr)); + if (copy_from_user(bufr, buf, datalen-1)) + return -EFAULT; + if ( strcmp(bufr, "dump") != 0 ) + return 0; + + pr_info("SGX %s keys:\n", type); + mutex_lock(lock); + list_for_each_entry(kp, keylist, list) { + pr_info("SGX %s: %*phN\n", type, KEY_SIZE, kp->key); + } + mutex_unlock(lock); + + return datalen; +} + +static int open_launch_keys(struct inode * inode, struct file * filp) +{ + if (!(filp->f_flags & O_WRONLY)) + return -EACCES; + if (atomic_dec_and_test(&launch_keys_opencount)) + return 0; + return -EBUSY; +} + +static ssize_t write_launch_keys(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + ssize_t retn; + + if (launch_keys_locked) { + retn = -EINVAL; + goto done; + } + + if (*ppos != 0) { + retn = -EINVAL; + goto done; + } + + retn = process_lock(buf, datalen, &launch_keys_locked); + if (retn != 0) + return retn; + + retn = process_clear(buf, datalen, "launch control", + &launch_keys_count, &launch_key_list_mutex, + &launch_key_list); + if (retn != 0) + return retn; + + retn = process_dump(buf, datalen, "lc", &launch_key_list_mutex, + &launch_key_list); + if (retn != 0) + return retn; + + retn = process_write_key(buf, datalen, &launch_keys_count, + &launch_key_list_mutex, &launch_key_list); + +done: + return retn; +} + +static int release_launch_keys(struct inode *inode, struct file *file) +{ + atomic_set(&launch_keys_opencount, 1); + return 0; +} + +static const struct file_operations launch_keys_ops = { + .open = open_launch_keys, + .write = write_launch_keys, + .release = release_launch_keys, + .llseek = generic_file_llseek, +}; + +/* Provisioning control. */ + +static int open_provision_keys(struct inode * inode, struct file * filp) +{ + if (!(filp->f_flags & O_WRONLY)) + return -EACCES; + if (atomic_dec_and_test(&provision_keys_opencount)) + return 0; + return -EBUSY; +} + +static ssize_t write_provision_keys(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + ssize_t retn; + + if (provision_keys_locked) { + retn = -EINVAL; + goto done; + } + + if (*ppos != 0) { + retn = -EINVAL; + goto done; + } + + retn = process_lock(buf, datalen, &provision_keys_locked); + if (retn != 0) + return retn; + + retn = process_clear(buf, datalen, "provisioning control", + &provision_keys_count, &provision_key_list_mutex, + &provision_key_list); + if (retn != 0) + return retn; + + retn = process_dump(buf, datalen, "pc", &provision_key_list_mutex, + &provision_key_list); + if (retn != 0) + return retn; + + retn = process_write_key(buf, datalen, &provision_keys_count, + &provision_key_list_mutex, + &provision_key_list); +done: + return retn; +} + +static int release_provision_keys(struct inode *inode, struct file *file) +{ + atomic_set(&provision_keys_opencount, 1); + return 0; +} + +static const struct file_operations provision_keys_ops = { + .open = open_provision_keys, + .write = write_provision_keys, + .release = release_provision_keys, + .llseek = generic_file_llseek, +}; + +/* + * Signing control. + */ + +static int open_signing_keys(struct inode * inode, struct file * filp) +{ + if (!(filp->f_flags & O_WRONLY)) + return -EACCES; + if (atomic_dec_and_test(&signing_keys_opencount)) + return 0; + return -EBUSY; +} + +static ssize_t write_signing_keys(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + ssize_t retn; + + if (signing_keys_locked) { + retn = -EINVAL; + goto done; + } + + if (*ppos != 0) { + retn = -EINVAL; + goto done; + } + + retn = process_lock(buf, datalen, &signing_keys_locked); + if (retn != 0) + return retn; + + retn = process_clear(buf, datalen, "signing control", + &signing_keys_count, &signing_key_list_mutex, + &signing_key_list); + if (retn != 0) + return retn; + + retn = process_dump(buf, datalen, "sc", &signing_key_list_mutex, + &signing_key_list); + if (retn != 0) + return retn; + + retn = process_write_key(buf, datalen, &signing_keys_count, + &signing_key_list_mutex, &signing_key_list); +done: + return retn; +} + +static int release_signing_keys(struct inode *inode, struct file *file) +{ + atomic_set(&signing_keys_opencount, 1); + return 0; +} + +static const struct file_operations signing_keys_ops = { + .open = open_signing_keys, + .write = write_signing_keys, + .release = release_signing_keys, + .llseek = generic_file_llseek, +}; + +int sgx_policy_fs(void) +{ + int retn = -1; + + sgx_fs = securityfs_create_dir("sgx", NULL); + if(IS_ERR(sgx_fs)) { + retn = PTR_ERR(sgx_fs); + goto err; + } + + launch_keys = securityfs_create_file("launch_keys", S_IWUSR | S_IRUSR, + sgx_fs, NULL, &launch_keys_ops); + if (IS_ERR(launch_keys)) { + retn = PTR_ERR(launch_keys); + goto err; + } + + provision_keys = securityfs_create_file("provisioning_keys", + S_IWUSR | S_IRUSR, + sgx_fs, NULL, + &provision_keys_ops); + if (IS_ERR(provision_keys)) { + retn = PTR_ERR(provision_keys); + goto err; + } + + signing_keys = securityfs_create_file("signing_keys", + S_IWUSR | S_IRUSR, + sgx_fs, NULL, + &signing_keys_ops); + if (IS_ERR(signing_keys)) { + retn = PTR_ERR(signing_keys); + goto err; + } + + return 0; + + err: + securityfs_remove(launch_keys); + securityfs_remove(provision_keys); + securityfs_remove(signing_keys); + securityfs_remove(sgx_fs); + return retn; +} + +void sgx_policy_fs_remove(void) +{ + struct list_key *kp, *kp_tmp; + + mutex_lock(&launch_key_list_mutex); + list_for_each_entry_safe(kp, kp_tmp, &launch_key_list, list) { + list_del(&kp->list); + kfree(kp); + } + mutex_unlock(&launch_key_list_mutex); + + mutex_lock(&provision_key_list_mutex); + list_for_each_entry_safe(kp, kp_tmp, &provision_key_list, list) { + list_del(&kp->list); + kfree(kp); + } + mutex_unlock(&provision_key_list_mutex); + + mutex_lock(&signing_key_list_mutex); + list_for_each_entry_safe(kp, kp_tmp, &signing_key_list, list) { + list_del(&kp->list); + kfree(kp); + } + mutex_unlock(&signing_key_list_mutex); + + securityfs_remove(launch_keys); + securityfs_remove(provision_keys); + securityfs_remove(signing_keys); + securityfs_remove(sgx_fs); +} diff --git a/arch/x86/kernel/cpu/sgx/main.c b/arch/x86/kernel/cpu/sgx/main.c index 07adb35c260b..c5a9df27702c 100644 --- a/arch/x86/kernel/cpu/sgx/main.c +++ b/arch/x86/kernel/cpu/sgx/main.c @@ -189,6 +189,8 @@ static void sgx_update_lepubkeyhash_msrs(u64 *lepubkeyhash, bool enforce) * @token: a pointer an EINITTOKEN (optional) * @secs: a pointer a SECS * @lepubkeyhash: the desired value for IA32_SGXLEPUBKEYHASHx MSRs + * @launch_signer: a pointer to a function returning possible + * launch signers * * Execute ENCLS[EINIT], writing the IA32_SGXLEPUBKEYHASHx MSRs according * to @lepubkeyhash (if possible and necessary). @@ -198,13 +200,30 @@ static void sgx_update_lepubkeyhash_msrs(u64 *lepubkeyhash, bool enforce) * -errno or SGX error on failure */ int sgx_einit(struct sgx_sigstruct *sigstruct, struct sgx_einittoken *token, - struct sgx_epc_page *secs, u64 *lepubkeyhash) + struct sgx_epc_page *secs, u64 *lepubkeyhash, + uint8_t * (*launch_signer)(uint8_t *)) { int ret; + uint8_t *signer; + + if (launch_signer) { + signer = (*launch_signer)(NULL); + while (signer) { + pr_debug("%s: Trying signer: %*phN\n", __FILE__, 32, + signer); + preempt_disable(); + sgx_update_lepubkeyhash_msrs((u64 *) signer, true); + ret = __einit(sigstruct, token, sgx_epc_addr(secs)); + preempt_enable(); + if (!ret) + return ret; + signer = (*launch_signer)(signer); + } + return ret; + } - if (!boot_cpu_has(X86_FEATURE_SGX_LC)) - return __einit(sigstruct, token, sgx_epc_addr(secs)); - + pr_debug("%s: Setting LC register for EINIT to: %*phN.\n", __FILE__, + 32, lepubkeyhash); preempt_disable(); sgx_update_lepubkeyhash_msrs(lepubkeyhash, false); ret = __einit(sigstruct, token, sgx_epc_addr(secs)); diff --git a/arch/x86/kernel/cpu/sgx/sgx.h b/arch/x86/kernel/cpu/sgx/sgx.h index 8a1dff1e5e8a..50fc9ee9d556 100644 --- a/arch/x86/kernel/cpu/sgx/sgx.h +++ b/arch/x86/kernel/cpu/sgx/sgx.h @@ -85,6 +85,7 @@ struct sgx_epc_page *sgx_alloc_page(void *owner, bool reclaim); int __sgx_free_page(struct sgx_epc_page *page); void sgx_free_page(struct sgx_epc_page *page); int sgx_einit(struct sgx_sigstruct *sigstruct, struct sgx_einittoken *token, - struct sgx_epc_page *secs, u64 *lepubkeyhash); + struct sgx_epc_page *secs, u64 *lepubkeyhash, + uint8_t *(*get_signer)(uint8_t *)); #endif /* _X86_SGX_H */ -----BEGIN PGP SIGNATURE----- iQEcBAABAgAGBQJct1mVAAoJEP1SNsFLeja67mYIAKWtG2bydNO9J9aMYBHegc/b 0uMms4rP6o5YRDoOVKMMyP5etoTQ2jRv1RUEukghnvHyMdEaad2JXcASiWmzrXTy lQXQlb8ejIc6C2PpCPmxB9pCV8ZtSTkoCsWgc4KvgjVtCJVbamx40CqvBoibcf+9 /nFKuqhXho163cT9PdKTWuxB5vgpaMUhtediEa2NhiTp6vfsCv9VZmWTf5OpTrIc h4ENGcHR6kJGnmbCTJlwzPgmZA2yK929MkFlOObhkexTG5xxTZlRxaveQX0QToz8 uzZ44c6a5IckNeD9yykyuC1vXdgV1tYlVJYGWhBZYMFwH2YkGsws7hlKfu51Tqw= =8RH8 -----END PGP SIGNATURE-----
On 4/18/19 10:10 AM, Dr. Greg wrote: > In addition, the driver breaks all existing SGX software by breaking > compatibility with what is a 3+ year ABI provided by the existing > driver. This seems to contravene the well understood philosophy that > Linux doesn't, if at all possible, break existing applications, Sorry, that doesn't apply to out-of-tree modules. While we don't go out of our way to intentionally break apps who are relying on out-of-tree modules, we also don't go our of or way to keep them working. Please stop asking about this. I don't see any route where it's going to change. Companies ideally shouldn't be getting their customers hooked on out-of-tree ABIs and customers should consume out-of-tree ABIs *expecting* them to break in the future.
On 4/18/19 10:10 AM, Dr. Greg wrote: > Both the current controls for enclave access to the PROVISION > attribute and the security controls that are being proposed to emerge > for the driver, sometime in the future, suffer from being dependent on > discretionary access controls, ie. file privileges, that can be > defeated by a privilege escalation attack. Those of us building > architectures on top of this technology have a need to certify that an > application will provide security contracts robust in the face of a > privilege escalation event or platform compromise. I'm not following. Are you saying that the implementation here is too permissive with the enclaves that are allowed to run? Because it's too permissive, this leaves us vulnerable to SGX being used to conceal a cache attack?
On Thu, Apr 18, 2019 at 10:11 AM Dr. Greg <greg@enjellic.com> wrote: > > On Wed, Apr 17, 2019 at 01:39:10PM +0300, Jarkko Sakkinen wrote: > > Good morning, I hope this note finds the impending end of the work > week going well for everyone. > > First of all, a thank you to Jarkko for his efforts in advancing this > driver, a process that can be decidedly thankless. Our apologies in > advance for contributing to this phenomenon. > > > Intel(R) SGX is a set of CPU instructions that can be used by > > applications to set aside private regions of code and data. The code > > outside the enclave is disallowed to access the memory inside the > > enclave by the CPU access control. In a way you can think that SGX > > provides inverted sandbox. It protects the application from a > > malicious host. > > Unfortunately, in its current implementation, the driver will not > protect a host from malicious enclaves. If it advances in its current > form, the mainline Linux kernel will be implementing a driver with > known and documented security issues. "I can use this kernel or CPU feature to do something unpleasant that I could do anyway, if less nicely" is a far cry from "known and documented security issues". We've hashed this out to death. Once SGX is upstream, feel free to submit patches. > > In addition, the driver breaks all existing SGX software by breaking > compatibility with what is a 3+ year ABI provided by the existing > driver. This seems to contravene the well understood philosophy that > Linux doesn't, if at all possible, break existing applications, > something that we believe can be easily prevented if those interested > in these issues choose to read on. As I understand it, Cedric is going to submit a patch for this shortly, assuming I have correctly guessed what supposed ABI break you're talking about. In the mean time, I need to correct you on a critical point. Linux has *never* had a policy that it will retain compatibility with an API that wasn't upstream in the first place. [removed extensive verbiage. Dr. Greg, if you want your emails to be read, please try to be more concise, and please try to repeat yourself less.] > The attached patch, against the jarkko-sgx/master branch of Jarkko's > driver repository, provides a framework that implements > cryptographically secure enclave initialization policies. The patch > and it's signature are also available from the following URL's: > > ftp://ftp.idfusion.net/pub/idfusion/jarkko-master-SFLC.patch I just spend several minutes trying to read your code. It would benefit dramatically from some attempt at documentation, and, at the very least, you can't have a function foo(type *ptr) that does something almost completely different when ptr == NULL and when ptr != NULL. For a concrete example, consider sgx_launch_control(). If you pass NULL, then there's a vaguely credible argument that the function does what the name suggests. If you pass non-NULL, it doesn't. The result is bug-prone and unreadable. If I'm understanding it right, it causes the SGX ABI to be almost completely incompatible between the case where the list of launch signers is empty and the case where the list is non-empty. This is a non-starter. We're also extensively discussed how launch enclaves would be supported if we were to support them, and the generally agreed-upon solution was that the *kernel* would handle running a launch enclave. Jarkko even has code for this, but it's tabled until someone comes up with a credible argument that we *want* to support launch enclaves. > The policy architecture allows just about any conceivable > initialization policy to be implemented and in any combination. No it doesn't, because it requires the application (or its runtime or SDK or whatever) to bundle the launch enclave that implements the fancy policy, which means that the platform owner actually can't swap out the policy without breaking all the applications. This is the primary reason that, if Linux were to support LE-based launch control, it would do so in the kernel. > If the platform owner chooses to do nothing, the driver will > initialize any enclave that is presented to it. FWIW, we have close > to a century of enterprise system's administration experience on our > team and we could not envision any competent system's administrator, > concerned about security, who would allow such a configuration. A sufficiently careful administrator who wants to protect against enclave malware might want the ability to revoke permission to run a specific enclave, which your approach can't do. Again, this has all been covered extensively. To be crystal clear, i fully agree that we are likely to eventually want some kind of in-kernel launch policy. But I don't think that defining such a policy should block the upstreaming of the driver, and I don't think that your proposal for how the policy should work is an acceptable proposal.
On Thu, Apr 18, 2019 at 11:01:00AM -0700, Dave Hansen wrote: Good morning to everyone. > On 4/18/19 10:10 AM, Dr. Greg wrote: > > Both the current controls for enclave access to the PROVISION > > attribute and the security controls that are being proposed to emerge > > for the driver, sometime in the future, suffer from being dependent on > > discretionary access controls, ie. file privileges, that can be > > defeated by a privilege escalation attack. Those of us building > > architectures on top of this technology have a need to certify that an > > application will provide security contracts robust in the face of a > > privilege escalation event or platform compromise. > I'm not following. > > Are you saying that the implementation here is too permissive with > the enclaves that are allowed to run? Because it's too permissive, > this leaves us vulnerable to SGX being used to conceal a cache > attack? I believe that would be the conclusion of a dispassionate observer who has followed this conversation and read the paper that I provided a link to. For the benefit of those with a disinclination to read, particularly 16 page research papers, the following link provides a summary of the issues at hand. https://www.securityweek.com/intel-sgx-can-be-abused-hide-advanced-malware-researchers Of relevance to this conversation is Intel Security's official response to the paper, which is as follows: "The value of Intel SGX is to execute code in a protected enclave; however, Intel SGX does not guarantee that the code executed in the enclave is from a trusted source. In all cases, we recommend utilizing programs, files, apps and plugins from trusted sources," Intel said. The issue is not as much the ABI break but the following facts that are at hand: 1.) The proposed mainline driver offers no cryptographic or architecturally relevant security controls for ensuring that enclaves are from a trusted source. 2.) Based on Andy's comments there may be a disinclination to ever provide those controls. 3.) The approach we propose addresses these issues while imposing no functional limitations on how Linux platform owners can use enclave technology. Seems like a win. There you go, one sentence replies. Dr. Greg As always, Dr. G.W. Wettstein, Ph.D. Enjellic Systems Development, LLC. 4206 N. 19th Ave. Specializing in information infra-structure Fargo, ND 58102 development. PH: 701-281-1686 FAX: 701-281-3949 EMAIL: greg@enjellic.com ------------------------------------------------------------------------------ "Laugh now but you won't be laughing when we find you laying on the side of the road dead." -- Betty Wettstein At the Lake
On 4/19/19 7:17 AM, Dr. Greg wrote: > 3.) The approach we propose addresses these issues while imposing no > functional limitations on how Linux platform owners can use enclave > technology. Let's say v20 gets merged. Is there any reason your approach can not be rebased on top of that code, reviewed and merged later?
> On Apr 19, 2019, at 7:17 AM, Dr. Greg <greg@enjellic.com> wrote: > > On Thu, Apr 18, 2019 at 11:01:00AM -0700, Dave Hansen wrote: > "The value of Intel SGX is to execute code in a protected enclave; > however, Intel SGX does not guarantee that the code executed in the > enclave is from a trusted source. In all cases, we recommend > utilizing programs, files, apps and plugins from trusted sources," > Intel said. Linux *has* mechanisms to enforce the provenance of code, and they have nothing to do with SGX. Off the top of my head, there’s IMA, SELinux (depending on policy), and dm-verity. So it seems to me that our bases are already pretty well covered. I see two cases where some additional protection for SGX might make sense: 1. You care more about the provenance of enclaves than the provenance of normal code. (“You” is the platform owner, not a remote party verifying SGX quotes.”) There are any number of solutions that could work here, and not all of them involve crypto. 2. You care about the case where the kernel is compromised. In this case, nothing that's been discussed helps much on an FLC system, and even the pre-LC systems aren't a whole lot better given the lack of init token revocation. But I think we may be missing a much bigger issue that does need consideration before the driver gets merged. We're all focusing on *additional* SGX protections, but I'm not even sure we have the SGX protections up to snuff with the rest of the system. There are many, many Linux systems that enforce a policy that *all* executable text needs to come from a verified source. On these systems, you can't mmap some writable memory, write to it, and then change it to executable. (Obviously, JITs either don't work or need special permissions on these systems.) Unless I'm missing it, the current SGX API is entirely incompatible with this model -- the host process supplies text *bytes* to the kernel, and the kernel merrily loads those bytes into executable enclave memory. Whoops! I think we may need to change the API so that enclaves are loaded from a file where the contents of the file are in some appropriate format. (The file should, at least, contain MRENCLAVE, but various antivirus tools would much prefer if the actual enclave contents were in the file.) It's not entirely clear that the enclave text and data need to be in the file, since they're covered by the hash.) Then, to start an enclave, you pass an fd to the file to the SGX driver, and the SGX driver parses out the relevant data initializes the enclave. Before this happens, the driver could call into IMA and LSM hooks, and the driver would also verify that the file didn't come from a noexec filesystem. I suppose another approach would be to treat SGX the same way that ld.so is treated, mostly by requiring that the buffers passed to the driver that contain text be marked executable. This seems quite a bit weakter to me. What do you all think?
On Thu, Apr 18, 2019 at 10:24:42AM -0700, Dave Hansen wrote: Good morning again. > On 4/18/19 10:10 AM, Dr. Greg wrote: > > In addition, the driver breaks all existing SGX software by breaking > > compatibility with what is a 3+ year ABI provided by the existing > > driver. This seems to contravene the well understood philosophy that > > Linux doesn't, if at all possible, break existing applications, > Sorry, that doesn't apply to out-of-tree modules. While we don't go > out of our way to intentionally break apps who are relying on > out-of-tree modules, we also don't go our of or way to keep them > working. Yes, there is no question that we understand this concept. The salient point is that when given an opportunity to preserve and transition an existing development community, provide an architecturally relevant driver and to impose no restrictions on how a new, as yet untested and undesigned security architecture can emerge, the decision is made to break all compatibility. > Please stop asking about this. I don't see any route where it's going > to change. Which goes to my first e-mail where I noted this was about idealogy rather then technology. Nothing wrong with that as long as we are intellectually honest. > Companies ideally shouldn't be getting their customers hooked on > out-of-tree ABIs and customers should consume out-of-tree ABIs > *expecting* them to break in the future. At the risk of being indelicate, it was your company that hooked the SGX development community on out-of-tree driver ABI's and software. We are just trying to find a mutually beneficial and productive path forward. On that note. One of the issues we have raised in multiple missives, that remains unaddressed, was the notion that the proposed driver may not work on all SGX hardware moving forward. Is there going to be an OEM mandated requirement, enforced by Intel licensing, that all SGX capable platforms will implement Flexible Launch Control? For those following along at home, here is a link to the Intel Security announcements made at RSA-2019 in February: https://newsroom.intel.com/news/rsa-2019-intel-partner-ecosystem-offer-new-silicon-enabled-security-solutions/ Of relevant note is the section 'Operational Control': "Intel is delivering a new capability called flexible launch control that enables a company's data center operations to set and manage their own unique security policies for launching enclaves as well as providing controlled access to sensitive platform identification information. This capability is currently available on Intel SGX-enabled Intel Xeon E Processors and some Intel NUC's". FLC is primarily about supporting Data-Center Attestation Services (DCAS) on XEON class servers. New technologies are released on NUC's since those are the platforms that Intel seems to target for developer experimentation. We have had some experience with legal and liability sensitivities surrounding security in general and SGX in particular. Absent an official policy statement, it is a really open question whether this driver will be universally useful, with the end result being a fair amount of chaos for the Linux SGX community. As opposed to Windows, which will have a known and stable ABI that works on any SGX capable hardware. As I noted in my first e-mail yesterday, we anticipated this and our architecture provides a path forward for resolving this issue as well. Have a good weekend. Dr. Greg As always, Dr. G.W. Wettstein, Ph.D. Enjellic Systems Development, LLC. 4206 N. 19th Ave. Specializing in information infra-structure Fargo, ND 58102 development. PH: 701-281-1686 FAX: 701-281-3949 EMAIL: greg@enjellic.com ------------------------------------------------------------------------------ "If you get to thinkin' you're a person of some influence, try orderin' somebody else's dog around." -- Cowboy Wisdom
On 4/19/19 9:24 AM, Dr. Greg wrote: >> Companies ideally shouldn't be getting their customers hooked on >> out-of-tree ABIs and customers should consume out-of-tree ABIs >> *expecting* them to break in the future. > At the risk of being indelicate, it was your company that hooked the > SGX development community on out-of-tree driver ABI's and software. I would encourage anyone who has been impacted by this to communicate that back to the folks at Intel from whom they received the out-of-tree code about the impact. > Is there going to be an OEM mandated requirement, enforced by Intel > licensing, that all SGX capable platforms will implement Flexible > Launch Control? Heck if I know. I don't think LKML is a great place to discuss Intel licensing requirements. What I *do* know is that when builds a platform without Flexible Launch Control, Linux does not support SGX on that platform. I guess that could be spelled out in some documentation explicitly, if it isn't already.
On 2019-04-19 08:27, Andy Lutomirski wrote: > There are many, > many Linux systems that enforce a policy that *all* executable text > needs to come from a verified source. On these systems, you can't > mmap some writable memory, write to it, and then change it to > executable. How is this implemented on those systems? AFAIK there's no kernel config option that changes the semantics of mmap as you describe. -- Jethro Beekman | Fortanix
On Fri, 19 Apr 2019, Jethro Beekman wrote: > On 2019-04-19 08:27, Andy Lutomirski wrote: > > There are many, > > many Linux systems that enforce a policy that *all* executable text > > needs to come from a verified source. On these systems, you can't > > mmap some writable memory, write to it, and then change it to > > executable. > > How is this implemented on those systems? AFAIK there's no kernel config > option that changes the semantics of mmap as you describe. That has nothing to do with mmap() semantics. You mmap() writeable memory and then you change the permissions via mprotect(). mprotect() calls into LSM and depending on policy and security model this will reject the request. Andy was pointing out that the SGX ioctl bypasses the LSM mechanics which is obviously a bad thing. Thanks, tglx
On 2019-04-19 13:39, Thomas Gleixner wrote: > On Fri, 19 Apr 2019, Jethro Beekman wrote: > >> On 2019-04-19 08:27, Andy Lutomirski wrote: >>> There are many, >>> many Linux systems that enforce a policy that *all* executable text >>> needs to come from a verified source. On these systems, you can't >>> mmap some writable memory, write to it, and then change it to >>> executable. >> >> How is this implemented on those systems? AFAIK there's no kernel config >> option that changes the semantics of mmap as you describe. > > That has nothing to do with mmap() semantics. You mmap() writeable memory > and then you change the permissions via mprotect(). mprotect() calls into > LSM and depending on policy and security model this will reject the > request. > > Andy was pointing out that the SGX ioctl bypasses the LSM mechanics which > is obviously a bad thing. We could modify the driver such that when you call ioctl EADD, the page table permissions need to be the PAGEINFO.SECINFO.FLAGS | PROT_WRITE, otherwise you get EPERM or so. After EADD, if you want, you can restrict the page table permissions again using mprotect but the page table permissions don't really matter for SGX. -- Jethro Beekman | Fortanix
On Fri, 19 Apr 2019, Jethro Beekman wrote: > On 2019-04-19 13:39, Thomas Gleixner wrote: > > On Fri, 19 Apr 2019, Jethro Beekman wrote: > > > >> On 2019-04-19 08:27, Andy Lutomirski wrote: > >>> There are many, > >>> many Linux systems that enforce a policy that *all* executable text > >>> needs to come from a verified source. On these systems, you can't > >>> mmap some writable memory, write to it, and then change it to > >>> executable. > >> > >> How is this implemented on those systems? AFAIK there's no kernel config > >> option that changes the semantics of mmap as you describe. > > > > That has nothing to do with mmap() semantics. You mmap() writeable memory > > and then you change the permissions via mprotect(). mprotect() calls into > > LSM and depending on policy and security model this will reject the > > request. > > > > Andy was pointing out that the SGX ioctl bypasses the LSM mechanics which > > is obviously a bad thing. > > We could modify the driver such that when you call ioctl EADD, the page > table permissions need to be the PAGEINFO.SECINFO.FLAGS | PROT_WRITE, > otherwise you get EPERM or so. After EADD, if you want, you can restrict > the page table permissions again using mprotect but the page table > permissions don't really matter for SGX. And the point of that is? That you still can cirumvent LSM for feeding executable code into SGX. No, we are not making special cases and exceptions for SGX. Thanks, tglx
On 2019-04-19 13:50, Thomas Gleixner wrote: > On Fri, 19 Apr 2019, Jethro Beekman wrote: >> On 2019-04-19 13:39, Thomas Gleixner wrote: >>> On Fri, 19 Apr 2019, Jethro Beekman wrote: >>> >>>> On 2019-04-19 08:27, Andy Lutomirski wrote: >>>>> There are many, >>>>> many Linux systems that enforce a policy that *all* executable text >>>>> needs to come from a verified source. On these systems, you can't >>>>> mmap some writable memory, write to it, and then change it to >>>>> executable. >>>> >>>> How is this implemented on those systems? AFAIK there's no kernel config >>>> option that changes the semantics of mmap as you describe. >>> >>> That has nothing to do with mmap() semantics. You mmap() writeable memory >>> and then you change the permissions via mprotect(). mprotect() calls into >>> LSM and depending on policy and security model this will reject the >>> request. >>> >>> Andy was pointing out that the SGX ioctl bypasses the LSM mechanics which >>> is obviously a bad thing. >> >> We could modify the driver such that when you call ioctl EADD, the page >> table permissions need to be the PAGEINFO.SECINFO.FLAGS | PROT_WRITE, >> otherwise you get EPERM or so. After EADD, if you want, you can restrict >> the page table permissions again using mprotect but the page table >> permissions don't really matter for SGX. > > And the point of that is? That you still can cirumvent LSM for feeding > executable code into SGX. How? LSM would see that you're trying to map a page RWX so you can do your ioctl? > No, we are not making special cases and exceptions for SGX. Maybe I didn't express myself clearly? I don't think I was suggesting anything like that. -- Jethro Beekman | Fortanix
On 2019-04-19 13:46, Jethro Beekman wrote: > On 2019-04-19 13:39, Thomas Gleixner wrote: >> On Fri, 19 Apr 2019, Jethro Beekman wrote: >> >>> On 2019-04-19 08:27, Andy Lutomirski wrote: >>>> There are many, >>>> many Linux systems that enforce a policy that *all* executable text >>>> needs to come from a verified source. On these systems, you can't >>>> mmap some writable memory, write to it, and then change it to >>>> executable. >>> >>> How is this implemented on those systems? AFAIK there's no kernel config >>> option that changes the semantics of mmap as you describe. >> >> That has nothing to do with mmap() semantics. You mmap() writeable memory >> and then you change the permissions via mprotect(). mprotect() calls into >> LSM and depending on policy and security model this will reject the >> request. >> >> Andy was pointing out that the SGX ioctl bypasses the LSM mechanics which >> is obviously a bad thing. > > We could modify the driver such that when you call ioctl EADD, the page > table permissions need to be the PAGEINFO.SECINFO.FLAGS | PROT_WRITE, > otherwise you get EPERM or so. After EADD, if you want, you can restrict Actually, I don't think you even need to include PAGEINFO.SECINFO.FLAGS, you just need to ensure PROT_WRITE. Regular page table checks take care of PAGEINFO.SECINFO.FLAGS. -- Jethro Beekman | Fortanix
> On Apr 19, 2019, at 1:54 PM, Jethro Beekman <jethro@fortanix.com> wrote: > >> On 2019-04-19 13:50, Thomas Gleixner wrote: >>> On Fri, 19 Apr 2019, Jethro Beekman wrote: >>>> On 2019-04-19 13:39, Thomas Gleixner wrote: >>>>> On Fri, 19 Apr 2019, Jethro Beekman wrote: >>>>> >>>>>> On 2019-04-19 08:27, Andy Lutomirski wrote: >>>>>> There are many, >>>>>> many Linux systems that enforce a policy that *all* executable text >>>>>> needs to come from a verified source. On these systems, you can't >>>>>> mmap some writable memory, write to it, and then change it to >>>>>> executable. >>>>> >>>>> How is this implemented on those systems? AFAIK there's no kernel config >>>>> option that changes the semantics of mmap as you describe. >>>> >>>> That has nothing to do with mmap() semantics. You mmap() writeable memory >>>> and then you change the permissions via mprotect(). mprotect() calls into >>>> LSM and depending on policy and security model this will reject the >>>> request. >>>> >>>> Andy was pointing out that the SGX ioctl bypasses the LSM mechanics which >>>> is obviously a bad thing. >>> >>> We could modify the driver such that when you call ioctl EADD, the page >>> table permissions need to be the PAGEINFO.SECINFO.FLAGS | PROT_WRITE, >>> otherwise you get EPERM or so. After EADD, if you want, you can restrict >>> the page table permissions again using mprotect but the page table >>> permissions don't really matter for SGX. >> >> And the point of that is? That you still can cirumvent LSM for feeding >> executable code into SGX. > > How? LSM would see that you're trying to map a page RWX so you can do > your ioctl? With plain mmap() + mprotect(), the LSM will prevent you from making memory that *was* writable executable. This is by design and SELinux supports it. I don’t remember the name of the associated SELinux permission off the top of my head. If we start enforcing equivalent rules on SGX, then the current API will simply not allow enclaves to be loaded — no matter how you slice it, loading an enclave with the current API is indistinguishable from making arbitrary data executable. Put another way, you can compile your enclave, ship it as a file, and get it appropriately verified (by LSM attribute, IMA, dm-verity, whatever) and run it, but, with the current API, the kernel has no way of knowing that the userspace enclave loader is actually reading it from the file in question. So I think we need to work on the API. > >> No, we are not making special cases and exceptions for SGX. > > Maybe I didn't express myself clearly? I don't think I was suggesting > anything like that. > > -- > Jethro Beekman | Fortanix
Jethro Beekman | Fortanix On 2019-04-19 14:15, Andy Lutomirski wrote: > > > > >> On Apr 19, 2019, at 1:54 PM, Jethro Beekman <jethro@fortanix.com> wrote: >> >>> On 2019-04-19 13:50, Thomas Gleixner wrote: >>>> On Fri, 19 Apr 2019, Jethro Beekman wrote: >>>>> On 2019-04-19 13:39, Thomas Gleixner wrote: >>>>>> On Fri, 19 Apr 2019, Jethro Beekman wrote: >>>>>> >>>>>>> On 2019-04-19 08:27, Andy Lutomirski wrote: >>>>>>> There are many, >>>>>>> many Linux systems that enforce a policy that *all* executable text >>>>>>> needs to come from a verified source. On these systems, you can't >>>>>>> mmap some writable memory, write to it, and then change it to >>>>>>> executable. >>>>>> >>>>>> How is this implemented on those systems? AFAIK there's no kernel config >>>>>> option that changes the semantics of mmap as you describe. >>>>> >>>>> That has nothing to do with mmap() semantics. You mmap() writeable memory >>>>> and then you change the permissions via mprotect(). mprotect() calls into >>>>> LSM and depending on policy and security model this will reject the >>>>> request. >>>>> >>>>> Andy was pointing out that the SGX ioctl bypasses the LSM mechanics which >>>>> is obviously a bad thing. >>>> >>>> We could modify the driver such that when you call ioctl EADD, the page >>>> table permissions need to be the PAGEINFO.SECINFO.FLAGS | PROT_WRITE, >>>> otherwise you get EPERM or so. After EADD, if you want, you can restrict >>>> the page table permissions again using mprotect but the page table >>>> permissions don't really matter for SGX. >>> >>> And the point of that is? That you still can cirumvent LSM for feeding >>> executable code into SGX. >> >> How? LSM would see that you're trying to map a page RWX so you can do >> your ioctl? > > With plain mmap() + mprotect(), the LSM will prevent you from making memory that *was* writable executable. This is by design and SELinux supports it. I don’t remember the name of the associated SELinux permission off the top of my head. > > If we start enforcing equivalent rules on SGX, then the current API will simply not allow enclaves to be loaded — no matter how you slice it, loading an enclave with the current API is indistinguishable from making arbitrary data executable. Yes this is exactly what I intended here: a very simple change that stops SGX from confusing LSM. Just by enforcing that everything that looks like a memory write (EADD, EAUG, EDBGWR, etc.) actually requires write permissions, reality and LSM should be on the same page. If you want to go further and actually allow this behavior when your LSM would otherwise prohibit it, presumably the same workarounds that exist for JITs can be used for SGX. -- Jethro Beekman | Fortanix
> On Apr 19, 2019, at 2:19 PM, Jethro Beekman <jethro@fortanix.com> wrote: > >> . >> >> If we start enforcing equivalent rules on SGX, then the current API will simply not allow enclaves to be loaded — no matter how you slice it, loading an enclave with the current API is indistinguishable from making arbitrary data executable. > > Yes this is exactly what I intended here: a very simple change that > stops SGX from confusing LSM. Just by enforcing that everything that > looks like a memory write (EADD, EAUG, EDBGWR, etc.) actually requires > write permissions, reality and LSM should be on the same page. > > If you want to go further and actually allow this behavior when your LSM > would otherwise prohibit it, presumably the same workarounds that exist > for JITs can be used for SGX. > > I do think we need to follow LSM rules. But my bigger point is that there are policies that don’t allow JIT at all. I think we should arrange the SGX API so it’s still usable when such a policy is in effect.
On Fri, 19 Apr 2019, Jethro Beekman wrote: > On 2019-04-19 14:15, Andy Lutomirski wrote: > > With plain mmap() + mprotect(), the LSM will prevent you from making > > memory that *was* writable executable. This is by design and SELinux > > supports it. I don’t remember the name of the associated SELinux > > permission off the top of my head. > > > > If we start enforcing equivalent rules on SGX, then the current API > > will simply not allow enclaves to be loaded — no matter how you slice > > it, loading an enclave with the current API is indistinguishable from > > making arbitrary data executable. > > > Yes this is exactly what I intended here: a very simple change that > stops SGX from confusing LSM. Just by enforcing that everything that > looks like a memory write (EADD, EAUG, EDBGWR, etc.) actually requires > write permissions, reality and LSM should be on the same page. And how so? You create writeable AND executable memory. That's a nono and you can argue in circles, that's not going to change with any of your proposed changes. Andy clearly made a proposal which solves it in a proper way. Thanks, tglx
On 2019-04-19 14:31, Andy Lutomirski wrote:
> I do think we need to follow LSM rules. But my bigger point is that there are policies that don’t allow JIT at all. I think we should arrange the SGX API so it’s still usable when such a policy is in effect.
I don't think we need to arrange that right now. This patch set needs to
be merged after more than 2 years of development. I'd like to avoid
introducing any more big changes. Let's just do what I described to make
LSM not broken, which is a minimal change to the current approach. We
can adjust the API later to support the use case you describe.
--
Jethro Beekman | Fortanix
On Fri, 19 Apr 2019, Jethro Beekman wrote: > On 2019-04-19 14:31, Andy Lutomirski wrote: > > I do think we need to follow LSM rules. But my bigger point is that > > there are policies that don’t allow JIT at all. I think we should > > arrange the SGX API so it’s still usable when such a policy is in > > effect. > I don't think we need to arrange that right now. This patch set needs to > be merged after more than 2 years of development. I'd like to avoid We merge stuff when it is ready and not when someone declares that it needs to be merged. > introducing any more big changes. Let's just do what I described to make > LSM not broken, which is a minimal change to the current approach. We > can adjust the API later to support the use case you describe. You are working around LSM nothing else and that's just not going to fly. Thanks, tglx
On 2019-04-19 14:34, Thomas Gleixner wrote: > And how so? You create writeable AND executable memory. That's a nono and > you can argue in circles, that's not going to change with any of your > proposed changes. On 2019-04-19 14:38, Thomas Gleixner wrote: > You are working around LSM nothing else and that's just not going to fly. Based on your comments, I'm still unsure if we're on the same page with regards to what I'm proposing. Here's a regular non-SGX flow that LSM would likely prevent: mmap(PROT_READ|PROT_WRITE) memcpy() mmap(PROT_READ|PROT_EXEC) <-- denied by LSM Or just something based on regular PT permissions: mmap(PROT_READ|PROT_EXEC) memcpy() <-- SIGSEGV Now, the equivalent for SGX: mmap(PROT_READ|PROT_WRITE) ioctl(EADD) mmap(PROT_READ|PROT_EXEC) <-- denied by LSM This works fine with v20 as-is. However, consider the equivalent of the PT-based flow: mmap(PROT_READ|PROT_EXEC) ioctl(EADD) <-- no error! It's not me that's working around the LSM, it's the SGX driver! It's writing to memory that's not marked writable! The fundamental issue here is that the SGX instruction set has several instructions that bypass the page table permission bits, and this is (naturally) confusing to any kind of reference monitor like the LSM framework. You can come up with similar scenarios that involve PROT_READ|PROT_WRITE|PROT_EXEC or ptrace(PTRACE_POKETEXT). So, clearly, the proper way to fix this failure of complete mediation is by enforcing appropriate page-table permissions even on the SGX instructions that don't do it themselves. Just make any implicit memory access look like a regular memory access and now everyone is on the same page (pun intended). -- Jethro Beekman | Fortanix
On Fri, 19 Apr 2019, Jethro Beekman wrote: > On 2019-04-19 14:34, Thomas Gleixner wrote: > > And how so? You create writeable AND executable memory. That's a nono and > > you can argue in circles, that's not going to change with any of your > > proposed changes. > > On 2019-04-19 14:38, Thomas Gleixner wrote: > > You are working around LSM nothing else and that's just not going to fly. > > Based on your comments, I'm still unsure if we're on the same page with > regards to what I'm proposing. > > Here's a regular non-SGX flow that LSM would likely prevent: > > mmap(PROT_READ|PROT_WRITE) > memcpy() > mmap(PROT_READ|PROT_EXEC) <-- denied by LSM > > Or just something based on regular PT permissions: > > mmap(PROT_READ|PROT_EXEC) > memcpy() <-- SIGSEGV > > Now, the equivalent for SGX: > > mmap(PROT_READ|PROT_WRITE) > ioctl(EADD) > mmap(PROT_READ|PROT_EXEC) <-- denied by LSM This is completely irrelevant, really. The point is that the SGX driver loads and executes arbitrary data which is handed in from user space via an ioctl w/o any chance of verifying where that comes from. What Andy proposed is to open a file with the SGX payload and hand in the file descriptor. That way LSM can decide whether this is allowed or denied based on the file descriptor and whatever the security model/policy is in a particular setup. Right know the SGX driver and its proposed API prevent any form of LSM auditing and whatever permission checks you had in mind won't change that at all. Thanks, tglx
On Sat, Apr 20, 2019 at 07:42:13AM +0200, Thomas Gleixner wrote: Good morning/weekend to everyone. > On Fri, 19 Apr 2019, Jethro Beekman wrote: > > On 2019-04-19 14:34, Thomas Gleixner wrote: > > > And how so? You create writeable AND executable memory. That's a nono and > > > you can argue in circles, that's not going to change with any of your > > > proposed changes. > > On 2019-04-19 14:38, Thomas Gleixner wrote: > > > You are working around LSM nothing else and that's just not going to fly. > > > > Based on your comments, I'm still unsure if we're on the same page with > > regards to what I'm proposing. > > > > Here's a regular non-SGX flow that LSM would likely prevent: > > > > mmap(PROT_READ|PROT_WRITE) > > memcpy() > > mmap(PROT_READ|PROT_EXEC) <-- denied by LSM > > > > Or just something based on regular PT permissions: > > > > mmap(PROT_READ|PROT_EXEC) > > memcpy() <-- SIGSEGV > > > > Now, the equivalent for SGX: > > > > mmap(PROT_READ|PROT_WRITE) > > ioctl(EADD) > > mmap(PROT_READ|PROT_EXEC) <-- denied by LSM > This is completely irrelevant, really. > > The point is that the SGX driver loads and executes arbitrary data > which is handed in from user space via an ioctl w/o any chance of > verifying where that comes from. > > What Andy proposed is to open a file with the SGX payload and hand > in the file descriptor. That way LSM can decide whether this is > allowed or denied based on the file descriptor and whatever the > security model/policy is in a particular setup. > > Right know the SGX driver and its proposed API prevent any form of > LSM auditing and whatever permission checks you had in mind won't > change that at all. I don't even want to remotely get in the middle of this arguement, since we are probably already persona-non-grata for having stimulated a discussion that has slowed upstreaming, but some comments to assist and clarify further progress on the driver. We understand and support the need for the LSM to trap these events, but what does LSM provenance mean if the platform is compromised? That is, technically, the target application for SGX technology. This issue is what drove our concern for having ring-0 based enforcement of who can initialize an enclave. As Andy has pointed out, the mmap semantics of the SGX driver allow the introduction of executable code that bypasses LSM enforcement, but the platform owner, with appropriate enforcements on an FLC platform, has cryptographic verification and trust for the origin and provenance of that executable code. It may be possible to load the executable code into enclave memory but the processor will refuse to access the memory until the EINIT instruction validates that the loaded code is compliant with the enclave metadata and measurement. That is why we were argueing for, simple, ring-0 based verification of the key signature that authorizes the EINIT instruction to complete. From a more practical perspective, an enclave shared image is a rather complex beast, that contains a lot of metadata. An application has to open the shared image file and parse the metadata in order to properly construct the enclave image from the text in the image file. I believe in practice that will effectively activate LSM recognition of the executable code. That obviously leaves a cunning adversary an opportunity to teleport a simple binary file or network stream onto a platform and load and execute the contents. But that takes us back to the need for ring-0 enforcement of enclave initialization... :-) We wrote a major enhancement to the Linux IMA architecture to implement introspection of behavioral namespaces. The actor/subject events get forwarded up into a modeling engine running inside of a namespace specific SGX enclave instance that issues a system call to instruct an LSM that we wrote, and that pairs with the IMA extension, to 'discipline' the process if it is acting outside its behavioral specification. We will verify with Jarkko's current HEAD, but I believe it is consistently trapping access to the shared image file and hence the text of the enclave. > Thanks, > > tglx Have a good weekend. Dr. Greg As always, Dr. G.W. Wettstein, Ph.D. Enjellic Systems Development, LLC. 4206 N. 19th Ave. Specializing in information infra-structure Fargo, ND 58102 development. PH: 701-281-1686 FAX: 701-281-3949 EMAIL: greg@enjellic.com ------------------------------------------------------------------------------ "Simplicity is prerequisite for reliability." -- Edsger W. Dijkstra
On Sat, Apr 20, 2019 at 11:02:47AM -0500, Dr. Greg wrote: > We understand and support the need for the LSM to trap these events, > but what does LSM provenance mean if the platform is compromised? > That is, technically, the target application for SGX technology. No, it's not. Protecting the kernel/platform from a malicious entity is outside the scope of SGX.
On Mon, Apr 22, 2019 at 08:01:19AM -0700, Sean Christopherson wrote: Good morning to everyone, I hope the week is starting well. > On Sat, Apr 20, 2019 at 11:02:47AM -0500, Dr. Greg wrote: > > We understand and support the need for the LSM to trap these > > events, but what does LSM provenance mean if the platform is > > compromised? That is, technically, the target application for SGX > > technology. > No, it's not. Protecting the kernel/platform from a malicious > entity is outside the scope of SGX. You must have misinterpreted my statement, providing security guarantees in the face of a compromised platform is exactly what SGX was designed to do and is how Intel is marketing the technology. From the first paragraph (Introduction) in the following document: https://software.intel.com/sites/default/files/managed/50/8c/Intel-SGX-Product-Brief.pdf "Intel Software Guard Extensions (Intel SGX) protects selected code and data from disclosure or modification. Developers can partition their application into CPU hardened 'enclaves' or protected areas of execution that increase security even on compromised platforms". In addition, one of the major use cases for this technology is the ability to push data and application code up onto cloud platforms with a guarantee that not even the platform owner or administrators can compromise the integrity or confidentiality of the code and data. As I've noted before, from an OS driver perspective, security and privacy models which are dependent on an uncompromised platform and user privileges are inconsistent with the SGX security architecture. Doing SGX right is about applying cryptographically defined provenance and integrity models. Our autonomous introspection technology uses SGX to protect the platform at large but we are unique with respect to how the technology is being applied. Have a good day. Dr. Greg As always, Dr. G.W. Wettstein, Ph.D. Enjellic Systems Development, LLC. 4206 N. 19th Ave. Specializing in information infra-structure Fargo, ND 58102 development. PH: 701-281-1686 FAX: 701-281-3949 EMAIL: greg@enjellic.com ------------------------------------------------------------------------------ "You and Uncle Pete drank the whole thing? That was a $250.00 bottle of whisky. Yeah, it was good." -- Rick Engen Resurrection.
> On Apr 19, 2019, at 2:56 PM, Jethro Beekman <jethro@fortanix.com> wrote: > >> On 2019-04-19 14:34, Thomas Gleixner wrote: >> And how so? You create writeable AND executable memory. That's a nono and >> you can argue in circles, that's not going to change with any of your >> proposed changes. > >> On 2019-04-19 14:38, Thomas Gleixner wrote: >> You are working around LSM nothing else and that's just not going to fly. > > Based on your comments, I'm still unsure if we're on the same page with > regards to what I'm proposing. > > Here's a regular non-SGX flow that LSM would likely prevent: > > mmap(PROT_READ|PROT_WRITE) > memcpy() > mmap(PROT_READ|PROT_EXEC) <-- denied by LSM > > Or just something based on regular PT permissions: > > mmap(PROT_READ|PROT_EXEC) > memcpy() <-- SIGSEGV > > Now, the equivalent for SGX: > > mmap(PROT_READ|PROT_WRITE) > ioctl(EADD) > mmap(PROT_READ|PROT_EXEC) <-- denied by LSM > > This works fine with v20 as-is. However, consider the equivalent of the > PT-based flow: > > mmap(PROT_READ|PROT_EXEC) > ioctl(EADD) <-- no error! Indeed! > > It's not me that's working around the LSM, it's the SGX driver! It's > writing to memory that's not marked writable! The fundamental issue here > is that the SGX instruction set has several instructions that bypass the > page table permission bits, and this is (naturally) confusing to any > kind of reference monitor like the LSM framework. You can come up with > similar scenarios that involve PROT_READ|PROT_WRITE|PROT_EXEC or > ptrace(PTRACE_POKETEXT). So, clearly, the proper way to fix this failure > of complete mediation is by enforcing appropriate page-table permissions > even on the SGX instructions that don't do it themselves. Just make any > implicit memory access look like a regular memory access and now > everyone is on the same page (pun intended). > I agree that we should do this. But then what? Once this gets fixed, the ioctl(EADD) fails and the driver becomes rather less useful, and we feel rather silly if we’ve merged it in this state. So I think we need a better EADD ioctl that explicitly does work on PROT_READ|PROT_EXEC enclave memory but makes up for by validating the *source* of the data. The effect will be similar to mapping a labeled, appraised, etc file as PROT_EXEC. Maybe, in extreme pseudocode: fd = open(“/dev/sgx/enclave”); ioctl(fd, SGX_CREATE_FROM_FILE, file_fd); // fd now inherits the LSM label from the file, or is otherwise marked. mmap(fd, PROT_READ|PROT_EXEC, ...); I suppose that an alternative would be to delegate all the EADD calls to a privileged daemon, but that’s nasty.
On Mon, Apr 22, 2019 at 11:24:11AM -0500, Dr. Greg wrote: > On Mon, Apr 22, 2019 at 08:01:19AM -0700, Sean Christopherson wrote: > > Good morning to everyone, I hope the week is starting well. > > > On Sat, Apr 20, 2019 at 11:02:47AM -0500, Dr. Greg wrote: > > > We understand and support the need for the LSM to trap these > > > events, but what does LSM provenance mean if the platform is > > > compromised? That is, technically, the target application for SGX > > > technology. > > > No, it's not. Protecting the kernel/platform from a malicious > > entity is outside the scope of SGX. > > You must have misinterpreted my statement, providing security > guarantees in the face of a compromised platform is exactly what SGX > was designed to do and is how Intel is marketing the technology. Right, and loading a malicious enclave doesn't change those guarantees (for other enclaves). Ergo, restricting which enclaves can execute is orthogonal to the security provided by SGX.
On Mon, Apr 22, 2019 at 9:48 AM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > Right, and loading a malicious enclave doesn't change those guarantees > (for other enclaves). Ergo, restricting which enclaves can execute is > orthogonal to the security provided by SGX. But it is absolutely worth noting that TSX made a lot of attacks both easier to _do_, and also easier to _hide_. All while being basically completely worthless technology to everybody except for some silly SAP benchmark. So it is definitely worth at least discussing the downsides of SGX. If it ends up being another technology that makes it easier to create malware, without actually having a lot of _good_ software use it, the patches to enable it should make damn sure that the upsides actually outweigh the downsides. And if the current setup basically is "you have to disable reasonable SElinux protections that lots of distros use today", I think it's entirely reasonable saying "the downsides are bigger than the upsides". Linus
On Mon, Apr 22, 2019 at 09:55:47AM -0700, Linus Torvalds wrote: > On Mon, Apr 22, 2019 at 9:48 AM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > Right, and loading a malicious enclave doesn't change those guarantees > > (for other enclaves). Ergo, restricting which enclaves can execute is > > orthogonal to the security provided by SGX. > > But it is absolutely worth noting that TSX made a lot of attacks both > easier to _do_, and also easier to _hide_. > > All while being basically completely worthless technology to everybody > except for some silly SAP benchmark. > > So it is definitely worth at least discussing the downsides of SGX. If > it ends up being another technology that makes it easier to create > malware, without actually having a lot of _good_ software use it, the > patches to enable it should make damn sure that the upsides actually > outweigh the downsides. > > And if the current setup basically is "you have to disable reasonable > SElinux protections that lots of distros use today", I think it's > entirely reasonable saying "the downsides are bigger than the > upsides". I'm not arguing against SGX playing nice with SELinux/LSMs, actually the opposite. I completely agree that enclaves should be subject to LSM restrictions. AIUI, Dr. Greg is proposing a framework that uses SGX's launch control mechanism to restrict what enclaves can run. My point is that restricting what enclaves can run is about protecting the kernel and/or platform, not the enclaves themselves, i.e. using launch control instead of, or in addition to, LSMs doesn't change the security guarantees of SGX.
The current proposed __vdso_sgx_enter_enclave() requires enclaves to preserve %rsp, which prohibits enclaves from allocating space on the untrusted stack. However, there are existing enclaves (e.g. those built with current Intel SGX SDK libraries) relying on the untrusted stack for passing parameters to untrusted functions (aka. o-calls), which requires allocating space on the untrusted stack by enclaves. And given its simplicity and convenience, it could be desired by future SGX applications as well. This patchset introduces a new ABI for __vdso_sgx_enter_enclave() to anchor its stack frame on %rbp (instead of %rsp), so as to allow enclaves to "push" onto the untrusted stack by decrementing the untrusted %rsp. Additionally, this new __vdso_sgx_enter_enclave() will take one more parameter - a callback function, to be invoked upon all enclave exits (both AEX and normal exits). The callback function will be given the value of %rsp left off by the enclave, so that data "pushed" by the enclave (if any) could be addressed/accessed. Please note that the callback function is optional, and if not supplied (i.e. null), __vdso_sgx_enter_enclave() will just return (i.e. behave the same as the current implementation) after the enclave exits (or AEX due to exceptions). The SGX selftest is augmented to test out the new callback interface, and to serve as a simple example to showcase how to use the callback interface in practice. Reference: * This patchset is based upon SGX1 patch v20 (https://lkml.org/lkml/2019/4/17/344) by Jarkko Sakkinen Cedric Xing (3): selftests/x86: Fixed Makefile for SGX selftest x86/vdso: Modify __vdso_sgx_enter_enclave() to allow parameter passing on untrusted stack selftests/x86: Augment SGX selftest to test new __vdso_sgx_enter_enclave() and its callback interface arch/x86/entry/vdso/vsgx_enter_enclave.S | 156 ++++++++++++--------- arch/x86/include/uapi/asm/sgx.h | 14 +- tools/testing/selftests/x86/Makefile | 12 +- tools/testing/selftests/x86/sgx/Makefile | 45 +++--- tools/testing/selftests/x86/sgx/main.c | 123 +++++++++++++--- tools/testing/selftests/x86/sgx/sgx_call.S | 40 +++++- 6 files changed, 264 insertions(+), 126 deletions(-)
On Mon, Apr 22, 2019 at 10:17:15AM -0700, Sean Christopherson wrote: Good morning to everyone. > On Mon, Apr 22, 2019 at 09:55:47AM -0700, Linus Torvalds wrote: > > On Mon, Apr 22, 2019 at 9:48 AM Sean Christopherson > > <sean.j.christopherson@intel.com> wrote: > > > > > > Right, and loading a malicious enclave doesn't change those guarantees > > > (for other enclaves). Ergo, restricting which enclaves can execute is > > > orthogonal to the security provided by SGX. > > > > But it is absolutely worth noting that TSX made a lot of attacks both > > easier to _do_, and also easier to _hide_. > > > > All while being basically completely worthless technology to everybody > > except for some silly SAP benchmark. > > > > So it is definitely worth at least discussing the downsides of SGX. If > > it ends up being another technology that makes it easier to create > > malware, without actually having a lot of _good_ software use it, the > > patches to enable it should make damn sure that the upsides actually > > outweigh the downsides. > > > > And if the current setup basically is "you have to disable reasonable > > SElinux protections that lots of distros use today", I think it's > > entirely reasonable saying "the downsides are bigger than the > > upsides". > I'm not arguing against SGX playing nice with SELinux/LSMs, actually > the opposite. I completely agree that enclaves should be subject to > LSM restrictions. As do we. The point we have been making is that depending on the LSM's are depending on the fact that the platform has not been compromised. SGX is designed to provide a trusted execution environment in the face of a compromised platform. > AIUI, Dr. Greg is proposing a framework that uses SGX's launch > control mechanism to restrict what enclaves can run. My point is > that restricting what enclaves can run is about protecting the > kernel and/or platform, not the enclaves themselves, i.e. using > launch control instead of, or in addition to, LSMs doesn't change > the security guarantees of SGX. I believe current research suggests that this is not the case. From the paper we have previously cited: https://arxiv.org/pdf/1702.08719.pdf In the second paragraph of the abstract: "In this paper, we demonstrate fine-grained software-based side-channel attacks from a malicious SGX enclave targeting co-located enclaves. Our attack is the first malware running on real SGX hardware, abusing SGX protection features to conceal itself. Furthermore, we demonstrate our attack both in a native environment and across multiple Docker containers". To be perfectly clear, Dr. Greg, technically IDfusion, is not proposing the use of SGX's launch control to restrict which enclaves can run, although there are perfectly legitimate and required use cases for that technology. Dr. Greg is proposing that the kernel driver expend 1.2 pages of kernel memory to implement, at the discretion of the platform owner, cryptographically verified enclave initialization. The design we proposed is the strongest guarantee that a platform owner can implement, on FLC hardware, that only code and data of known provenance can be loaded and executed. There are only two companies that have written the entire stack of software needed to make practical SGX applications work, us and Intel. We can go into intimate detail on the issues involved but will embrace bevity at this point. Have a good day. Dr. Greg As always, Dr. G.W. Wettstein, Ph.D. Enjellic Systems Development, LLC. 4206 N. 19th Ave. Specializing in information infra-structure Fargo, ND 58102 development. PH: 701-281-1686 FAX: 701-281-3949 EMAIL: greg@enjellic.com ------------------------------------------------------------------------------ "Because the innovator has for enemies all those who have done well under the old conditions, and lukewarm defenders in those who may do well under the new." -- Niccolo Machiavelli _The Prince_, Chapter VI
On Wed, Apr 17, 2019 at 01:39:10PM +0300, Jarkko Sakkinen wrote: > Intel(R) SGX is a set of CPU instructions that can be used by applications > to set aside private regions of code and data. The code outside the enclave > is disallowed to access the memory inside the enclave by the CPU access > control. In a way you can think that SGX provides inverted sandbox. It > protects the application from a malicious host. > > There is a new hardware unit in the processor called Memory Encryption > Engine (MEE) starting from the Skylake microacrhitecture. BIOS can define > one or many MEE regions that can hold enclave data by configuring them with > PRMRR registers. > > The MEE automatically encrypts the data leaving the processor package to > the MEE regions. The data is encrypted using a random key whose life-time > is exactly one power cycle. > > The current implementation requires that the firmware sets > IA32_SGXLEPUBKEYHASH* MSRs as writable so that ultimately the kernel can > decide what enclaves it wants run. The implementation does not create > any bottlenecks to support read-only MSRs later on. > > You can tell if your CPU supports SGX by looking into /proc/cpuinfo: > > cat /proc/cpuinfo | grep sgx > > v20: > * Fine-tune Kconfig messages and spacing and remove MMU_NOTIFIER > dependency as MMU notifiers are no longer used in the driver. > * Use mm_users instead of mm_count as refcount for mm_struct as mm_count > only protects from deleting mm_struct, not removing its contents. > * Sanitize EPC when the reclaimer thread starts by doing EREMOVE for all > of them. They could be in initialized state when the kernel starts > because it might be spawned by kexec(). > * Documentation overhaul. > * Use a device /dev/sgx/provision for delivering the provision token > instead of securityfs. > * Create a reference to the enclave when already when opening > /dev/sgx/enclave. The file is then associated with this enclave only. > mmap() can be done at free at any point and always get a reference to > the enclave. To summarize the file now represents the enclave. > > v19: > * Took 3-4 months but in some sense this was more like a rewrite of most > of the corners of the source code. If I've forgotten to deal with some > feedback, please don't shout me. Make a remark and I will fix it for > the next version. Hopefully there won't be this big turnovers anymore. > * Validate SECS attributes properly against CPUID given attributes and > against allowed attributes. SECS attributes are the ones that are > enforced whereas SIGSTRUCT attributes tell what is required to run > the enclave. > * Add KSS (Key Sharing Support) to the enclave attributes. > * Deny MAP_PRIVATE as an enclave is always a shared memory entity. > * Revert back to shmem backing storage so that it can be easily shared > by multiple processes. > * Split the recognization of an ENCLS leaf failure by using three > functions to detect it: encsl_faulted(), encls_returned_code() and > sgx_failed(). encls_failed() is only caused by a spurious expections that > should never happen. Thus, it is not defined as an inline function in > order to easily insert a kprobe to it. > * Move low-level enclave management routines, page fault handler and page > reclaiming routines from driver to the core. These cannot be separated > from each other as they are heavily interdependent. The rationale is that > the core does not call any code from the driver. > * Allow the driver to be compiled as a module now that it no code is using > its routines and it only uses exported symbols. Now the driver is > essentially just a thin ioctl layer. > * Reworked the driver to maintain a list of mm_struct's. The VMA callbacks > add new entries to this list as the process is forked. Each entry has > its own refcount because they have a different life-cycle as the enclave > does. In effect @tgid and @mm have been removed from struct sgx_encl > and we allow forking by removing VM_DONTCOPY from vm flags. > * Generate a cpu mask in the reclaimer from the cpu mask's of all > mm_struct's. This will kick out the hardware threads out of the enclave > from multiple processes. It is not a local variable because it would > eat too much of the stack space but instead a field in struct > sgx_encl. > * Allow forking i.e. remove VM_DONTCOPY. I did not change the API > because the old API scaled to the workload that Andy described. The > codebase is now mostly API independent i.e. changing the API is a > small task. For me the proper trigger to chanage it is a as concrete > as possible workload that cannot be fulfilled. I hope you understand > my thinking here. I don't want to change anything w/o proper basis > but I'm ready to change anything if there is a proper basis. I do > not have any kind of attachment to any particular type of API. > * Add Sean's vDSO ENCLS(EENTER) patches and update selftest to use the > new vDSO. > > v18: > * Update the ioctl-number.txt. > * Move the driver under arch/x86. > * Add SGX features (SGX, SGX1, SGX2) to the disabled-features.h. > * Rename the selftest as test_sgx (previously sgx-selftest). > * In order to enable process accounting, swap EPC pages and PCMD's to a VMA > instead of shmem. > * Allow only to initialize and run enclaves with a subset of > {DEBUG, MODE64BIT} set. > * Add SGX_IOC_ENCLAVE_SET_ATTRIBUTE to allow an enclave to have privileged > attributes e.g. PROVISIONKEY. > > v17: > * Add a simple selftest. > * Fix a null pointer dereference to section->pages when its > allocation fails. > * Add Sean's description of the exception handling to the documentation. > > v16: > * Fixed SOB's in the commits that were a bit corrupted in v15. > * Implemented exceptio handling properly to detect_sgx(). > * Use GENMASK() to define SGX_CPUID_SUB_LEAF_TYPE_MASK. > * Updated the documentation to use rst definition lists. > * Added the missing Documentation/x86/index.rst, which has a link to > intel_sgx.rst. Now the SGX and uapi documentation is properly generated > with 'make htmldocs'. > * While enumerating EPC sections, if an undefined section is found, fail > the driver initialization instead of continuing the initialization. > * Issue a warning if there are more than %SGX_MAX_EPC_SECTIONS. > * Remove copyright notice from arch/x86/include/asm/sgx.h. > * Migrated from ioremap_cache() to memremap(). > > v15: > * Split into more digestable size patches. > * Lots of small fixes and clean ups. > * Signal a "plain" SIGSEGV on an EPCM violation. > > v14: > * Change the comment about X86_FEATURE_SGX_LC from “SGX launch > configuration” to “SGX launch control”. > * Move the SGX-related CPU feature flags as part of the Linux defined > virtual leaf 8. > * Add SGX_ prefix to the constants defining the ENCLS leaf functions. > * Use GENMASK*() and BIT*() in sgx_arch.h instead of raw hex numbers. > * Refine the long description for CONFIG_INTEL_SGX_CORE. > * Do not use pr_*_ratelimited() in the driver. The use of the rate limited > versions is legacy cruft from the prototyping phase. > * Detect sleep with SGX_INVALID_EINIT_TOKEN instead of counting power > cycles. > * Manually prefix with “sgx:” in the core SGX code instead of redefining > pr_fmt. > * Report if IA32_SGXLEPUBKEYHASHx MSRs are not writable in the driver > instead of core because it is a driver requirement. > * Change prompt to bool in the entry for CONFIG_INTEL_SGX_CORE because the > default is ‘n’. > * Rename struct sgx_epc_bank as struct sgx_epc_section in order to match > the SDM. > * Allocate struct sgx_epc_page instances one at a time. > * Use “__iomem void *” pointers for the mapped EPC memory consistently. > * Retry once on SGX_INVALID_TOKEN in sgx_einit() instead of counting power > cycles. > * Call enclave swapping operations directly from the driver instead of > calling them .indirectly through struct sgx_epc_page_ops because indirect > calls are not required yet as the patch set does not contain the KVM > support. > * Added special signal SEGV_SGXERR to notify about SGX EPCM violation > errors. > > v13: > * Always use SGX_CPUID constant instead of a hardcoded value. > * Simplified and documented the macros and functions for ENCLS leaves. > * Enable sgx_free_page() to free active enclave pages on demand > in order to allow sgx_invalidate() to delete enclave pages. > It no longer performs EREMOVE if a page is in the process of > being reclaimed. > * Use PM notifier per enclave so that we don't have to traverse > the global list of active EPC pages to find enclaves. > * Removed unused SGX_LE_ROLLBACK constant from uapi/asm/sgx.h > * Always use ioremap() to map EPC banks as we only support 64-bit kernel. > * Invalidate IA32_SGXLEPUBKEYHASH cache used by sgx_einit() when going > to sleep. > > v12: > * Split to more narrow scoped commits in order to ease the review process and > use co-developed-by tag for co-authors of commits instead of listing them in > the source files. > * Removed cruft EXPORT_SYMBOL() declarations and converted to static variables. > * Removed in-kernel LE i.e. this version of the SGX software stack only > supports unlocked IA32_SGXLEPUBKEYHASHx MSRs. > * Refined documentation on launching enclaves, swapping and enclave > construction. > * Refined sgx_arch.h to include alignment information for every struct that > requires it and removed structs that are not needed without an LE. > * Got rid of SGX_CPUID. > * SGX detection now prints log messages about firmware configuration issues. > > v11: > * Polished ENCLS wrappers with refined exception handling. > * ksgxswapd was not stopped (regression in v5) in > sgx_page_cache_teardown(), which causes a leaked kthread after driver > deinitialization. > * Shutdown sgx_le_proxy when going to suspend because its EPC pages will be > invalidated when resuming, which will cause it not function properly > anymore. > * Set EINITTOKEN.VALID to zero for a token that is passed when > SGXLEPUBKEYHASH matches MRSIGNER as alloc_page() does not give a zero > page. > * Fixed the check in sgx_edbgrd() for a TCS page. Allowed to read offsets > around the flags field, which causes a #GP. Only flags read is readable. > * On read access memcpy() call inside sgx_vma_access() had src and dest > parameters in wrong order. > * The build issue with CONFIG_KASAN is now fixed. Added undefined symbols > to LE even if “KASAN_SANITIZE := false” was set in the makefile. > * Fixed a regression in the #PF handler. If a page has > SGX_ENCL_PAGE_RESERVED flag the #PF handler should unconditionally fail. > It did not, which caused weird races when trying to change other parts of > swapping code. > * EPC management has been refactored to a flat LRU cache and moved to > arch/x86. The swapper thread reads a cluster of EPC pages and swaps all > of them. It can now swap from multiple enclaves in the same round. > * For the sake of consistency with SGX_IOC_ENCLAVE_ADD_PAGE, return -EINVAL > when an enclave is already initialized or dead instead of zero. > > v10: > * Cleaned up anon inode based IPC between the ring-0 and ring-3 parts > of the driver. > * Unset the reserved flag from an enclave page if EDBGRD/WR fails > (regression in v6). > * Close the anon inode when LE is stopped (regression in v9). > * Update the documentation with a more detailed description of SGX. > > v9: > * Replaced kernel-LE IPC based on pipes with an anonymous inode. > The driver does not require anymore new exports. > > v8: > * Check that public key MSRs match the LE public key hash in the > driver initialization when the MSRs are read-only. > * Fix the race in VA slot allocation by checking the fullness > immediately after succeesful allocation. > * Fix the race in hash mrsigner calculation between the launch > enclave and user enclaves by having a separate lock for hash > calculation. > > v7: > * Fixed offset calculation in sgx_edbgr/wr(). Address was masked with PAGE_MASK > when it should have been masked with ~PAGE_MASK. > * Fixed a memory leak in sgx_ioc_enclave_create(). > * Simplified swapping code by using a pointer array for a cluster > instead of a linked list. > * Squeezed struct sgx_encl_page to 32 bytes. > * Fixed deferencing of an RSA key on OpenSSL 1.1.0. > * Modified TC's CMAC to use kernel AES-NI. Restructured the code > a bit in order to better align with kernel conventions. > > v6: > * Fixed semaphore underrun when accessing /dev/sgx from the launch enclave. > * In sgx_encl_create() s/IS_ERR(secs)/IS_ERR(encl)/. > * Removed virtualization chapter from the documentation. > * Changed the default filename for the signing key as signing_key.pem. > * Reworked EPC management in a way that instead of a linked list of > struct sgx_epc_page instances there is an array of integers that > encodes address and bank of an EPC page (the same data as 'pa' field > earlier). The locking has been moved to the EPC bank level instead > of a global lock. > * Relaxed locking requirements for EPC management. EPC pages can be > released back to the EPC bank concurrently. > * Cleaned up ptrace() code. > * Refined commit messages for new architectural constants. > * Sorted includes in every source file. > * Sorted local variable declarations according to the line length in > every function. > * Style fixes based on Darren's comments to sgx_le.c. > > v5: > * Described IPC between the Launch Enclave and kernel in the commit messages. > * Fixed all relevant checkpatch.pl issues that I have forgot fix in earlier > versions except those that exist in the imported TinyCrypt code. > * Fixed spelling mistakes in the documentation. > * Forgot to check the return value of sgx_drv_subsys_init(). > * Encapsulated properly page cache init and teardown. > * Collect epc pages to a temp list in sgx_add_epc_bank > * Removed SGX_ENCLAVE_INIT_ARCH constant. > > v4: > * Tied life-cycle of the sgx_le_proxy process to /dev/sgx. > * Removed __exit annotation from sgx_drv_subsys_exit(). > * Fixed a leak of a backing page in sgx_process_add_page_req() in the > case when vm_insert_pfn() fails. > * Removed unused symbol exports for sgx_page_cache.c. > * Updated sgx_alloc_page() to require encl parameter and documented the > behavior (Sean Christopherson). > * Refactored a more lean API for sgx_encl_find() and documented the behavior. > * Moved #PF handler to sgx_fault.c. > * Replaced subsys_system_register() with plain bus_register(). > * Retry EINIT 2nd time only if MSRs are not locked. > > v3: > * Check that FEATURE_CONTROL_LOCKED and FEATURE_CONTROL_SGX_ENABLE are set. > * Return -ERESTARTSYS in __sgx_encl_add_page() when sgx_alloc_page() fails. > * Use unused bits in epc_page->pa to store the bank number. > * Removed #ifdef for WQ_NONREENTRANT. > * If mmu_notifier_register() fails with -EINTR, return -ERESTARTSYS. > * Added --remove-section=.got.plt to objcopy flags in order to prevent a > dummy .got.plt, which will cause an inconsistent size for the LE. > * Documented sgx_encl_* functions. > * Added remark about AES implementation used inside the LE. > * Removed redundant sgx_sys_exit() from le/main.c. > * Fixed struct sgx_secinfo alignment from 128 to 64 bytes. > * Validate miscselect in sgx_encl_create(). > * Fixed SSA frame size calculation to take the misc region into account. > * Implemented consistent exception handling to __encls() and __encls_ret(). > * Implemented a proper device model in order to allow sysfs attributes > and in-kernel API. > * Cleaned up various "find enclave" implementations to the unified > sgx_encl_find(). > * Validate that vm_pgoff is zero. > * Discard backing pages with shmem_truncate_range() after EADD. > * Added missing EEXTEND operations to LE signing and launch. > * Fixed SSA size for GPRS region from 168 to 184 bytes. > * Fixed the checks for TCS flags. Now DBGOPTIN is allowed. > * Check that TCS addresses are in ELRANGE and not just page aligned. > * Require kernel to be compiled with X64_64 and CPU_SUP_INTEL. > * Fixed an incorrect value for SGX_ATTR_DEBUG from 0x01 to 0x02. > > v2: > * get_rand_uint32() changed the value of the pointer instead of value > where it is pointing at. > * Launch enclave incorrectly used sigstruct attributes-field instead of > enclave attributes-field. > * Removed unused struct sgx_add_page_req from sgx_ioctl.c > * Removed unused sgx_has_sgx2. > * Updated arch/x86/include/asm/sgx.h so that it provides stub > implementations when sgx in not enabled. > * Removed cruft rdmsr-calls from sgx_set_pubkeyhash_msrs(). > * return -ENOMEM in sgx_alloc_page() when VA pages consume too much space > * removed unused global sgx_nr_pids > * moved sgx_encl_release to sgx_encl.c > * return -ERESTARTSYS instead of -EINTR in sgx_encl_init() > > Jarkko Sakkinen (11): > x86/sgx: Add ENCLS architectural error codes > x86/sgx: Add SGX1 and SGX2 architectural data structures > x86/sgx: Add wrappers for ENCLS leaf functions > x86/sgx: Add functions to allocate and free EPC pages > x86/sgx: Add the Linux SGX Enclave Driver > x86/sgx: Add provisioning > x86/sgx: Add swapping code to the core and SGX driver > x86/sgx: ptrace() support for the SGX driver > selftests/x86: Add a selftest for SGX > x86/sgx: Update MAINTAINERS > docs: x86/sgx: Document the enclave API > > Kai Huang (2): > x86/cpufeatures: Add Intel-defined SGX feature bit > x86/cpufeatures: Add Intel-defined SGX_LC feature bit > > Sean Christopherson (15): > x86/cpufeatures: Add SGX sub-features (as Linux-defined bits) > x86/msr: Add IA32_FEATURE_CONTROL.SGX_ENABLE definition > x86/msr: Add SGX Launch Control MSR definitions > x86/mm: x86/sgx: Add new 'PF_SGX' page fault error code bit > x86/mm: x86/sgx: Signal SIGSEGV for userspace #PFs w/ PF_SGX > x86/cpu/intel: Detect SGX support and update caps appropriately > x86/sgx: Enumerate and track EPC sections > x86/sgx: Add sgx_einit() for initializing enclaves > x86/vdso: Add support for exception fixup in vDSO functions > x86/fault: Add helper function to sanitize error code > x86/fault: Attempt to fixup unhandled #PF in vDSO before signaling > x86/traps: Attempt to fixup exceptions in vDSO before signaling > x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave > transitions > docs: x86/sgx: Add Architecture documentation > docs: x86/sgx: Document kernel internals > > Documentation/index.rst | 1 + > Documentation/ioctl/ioctl-number.txt | 1 + > Documentation/x86/index.rst | 10 + > Documentation/x86/sgx/1.Architecture.rst | 431 +++++++++ > Documentation/x86/sgx/2.Kernel-internals.rst | 56 ++ > Documentation/x86/sgx/3.API.rst | 27 + > Documentation/x86/sgx/index.rst | 18 + > MAINTAINERS | 12 + > arch/x86/Kconfig | 27 + > arch/x86/entry/vdso/Makefile | 6 +- > arch/x86/entry/vdso/extable.c | 37 + > arch/x86/entry/vdso/extable.h | 29 + > arch/x86/entry/vdso/vdso-layout.lds.S | 9 +- > arch/x86/entry/vdso/vdso.lds.S | 1 + > arch/x86/entry/vdso/vdso2c.h | 58 +- > arch/x86/entry/vdso/vsgx_enter_enclave.S | 101 +++ > arch/x86/include/asm/cpufeatures.h | 24 +- > arch/x86/include/asm/disabled-features.h | 14 +- > arch/x86/include/asm/msr-index.h | 8 + > arch/x86/include/asm/traps.h | 1 + > arch/x86/include/asm/vdso.h | 5 + > arch/x86/include/uapi/asm/sgx.h | 86 ++ > arch/x86/include/uapi/asm/sgx_errno.h | 91 ++ > arch/x86/kernel/cpu/Makefile | 1 + > arch/x86/kernel/cpu/intel.c | 39 + > arch/x86/kernel/cpu/scattered.c | 2 + > arch/x86/kernel/cpu/sgx/Makefile | 2 + > arch/x86/kernel/cpu/sgx/arch.h | 424 +++++++++ > arch/x86/kernel/cpu/sgx/driver/Makefile | 3 + > arch/x86/kernel/cpu/sgx/driver/driver.h | 38 + > arch/x86/kernel/cpu/sgx/driver/ioctl.c | 850 ++++++++++++++++++ > arch/x86/kernel/cpu/sgx/driver/main.c | 368 ++++++++ > arch/x86/kernel/cpu/sgx/encl.c | 709 +++++++++++++++ > arch/x86/kernel/cpu/sgx/encl.h | 136 +++ > arch/x86/kernel/cpu/sgx/encls.c | 22 + > arch/x86/kernel/cpu/sgx/encls.h | 244 +++++ > arch/x86/kernel/cpu/sgx/main.c | 360 ++++++++ > arch/x86/kernel/cpu/sgx/reclaim.c | 482 ++++++++++ > arch/x86/kernel/cpu/sgx/sgx.h | 90 ++ > arch/x86/kernel/traps.c | 14 + > arch/x86/mm/fault.c | 44 +- > tools/arch/x86/include/asm/cpufeatures.h | 21 +- > tools/testing/selftests/x86/Makefile | 10 + > tools/testing/selftests/x86/sgx/Makefile | 48 + > tools/testing/selftests/x86/sgx/defines.h | 39 + > tools/testing/selftests/x86/sgx/encl.c | 20 + > tools/testing/selftests/x86/sgx/encl.lds | 33 + > .../selftests/x86/sgx/encl_bootstrap.S | 94 ++ > tools/testing/selftests/x86/sgx/encl_piggy.S | 18 + > tools/testing/selftests/x86/sgx/encl_piggy.h | 14 + > tools/testing/selftests/x86/sgx/main.c | 279 ++++++ > tools/testing/selftests/x86/sgx/sgx_call.S | 15 + > tools/testing/selftests/x86/sgx/sgxsign.c | 508 +++++++++++ > .../testing/selftests/x86/sgx/signing_key.pem | 39 + > 54 files changed, 5987 insertions(+), 32 deletions(-) > create mode 100644 Documentation/x86/index.rst > create mode 100644 Documentation/x86/sgx/1.Architecture.rst > create mode 100644 Documentation/x86/sgx/2.Kernel-internals.rst > create mode 100644 Documentation/x86/sgx/3.API.rst > create mode 100644 Documentation/x86/sgx/index.rst > create mode 100644 arch/x86/entry/vdso/extable.c > create mode 100644 arch/x86/entry/vdso/extable.h > create mode 100644 arch/x86/entry/vdso/vsgx_enter_enclave.S > create mode 100644 arch/x86/include/uapi/asm/sgx.h > create mode 100644 arch/x86/include/uapi/asm/sgx_errno.h > create mode 100644 arch/x86/kernel/cpu/sgx/Makefile > create mode 100644 arch/x86/kernel/cpu/sgx/arch.h > create mode 100644 arch/x86/kernel/cpu/sgx/driver/Makefile > create mode 100644 arch/x86/kernel/cpu/sgx/driver/driver.h > create mode 100644 arch/x86/kernel/cpu/sgx/driver/ioctl.c > create mode 100644 arch/x86/kernel/cpu/sgx/driver/main.c > create mode 100644 arch/x86/kernel/cpu/sgx/encl.c > create mode 100644 arch/x86/kernel/cpu/sgx/encl.h > create mode 100644 arch/x86/kernel/cpu/sgx/encls.c > create mode 100644 arch/x86/kernel/cpu/sgx/encls.h > create mode 100644 arch/x86/kernel/cpu/sgx/main.c > create mode 100644 arch/x86/kernel/cpu/sgx/reclaim.c > create mode 100644 arch/x86/kernel/cpu/sgx/sgx.h > create mode 100644 tools/testing/selftests/x86/sgx/Makefile > create mode 100644 tools/testing/selftests/x86/sgx/defines.h > create mode 100644 tools/testing/selftests/x86/sgx/encl.c > create mode 100644 tools/testing/selftests/x86/sgx/encl.lds > create mode 100644 tools/testing/selftests/x86/sgx/encl_bootstrap.S > create mode 100644 tools/testing/selftests/x86/sgx/encl_piggy.S > create mode 100644 tools/testing/selftests/x86/sgx/encl_piggy.h > create mode 100644 tools/testing/selftests/x86/sgx/main.c > create mode 100644 tools/testing/selftests/x86/sgx/sgx_call.S > create mode 100644 tools/testing/selftests/x86/sgx/sgxsign.c > create mode 100644 tools/testing/selftests/x86/sgx/signing_key.pem > > -- > 2.19.1 > I'm on leave for this week and next week's Monday if you wonder why I'm so passive in the discussion. Looking at the things next week's Tue. Just a quick comment about Andy's proposal. Probably pretty DSO like ELF blob could work with an addition of a section called ".tcs" for entry points. They need to be recognized so that the loader can add them as TCS pages. My self-test already is a PoC for enclave binary with a custom linker script to define the binary format. Too simplistic for a "generic" case but still a starting point. /Jarkko
On Tue, Apr 23, 2019 at 4:56 AM Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > > On Wed, Apr 17, 2019 at 01:39:10PM +0300, Jarkko Sakkinen wrote: > > Intel(R) SGX is a set of CPU instructions that can be used by applications > > to set aside private regions of code and data. The code outside the enclave > > is disallowed to access the memory inside the enclave by the CPU access > > control. In a way you can think that SGX provides inverted sandbox. It > > protects the application from a malicious host. > > > > There is a new hardware unit in the processor called Memory Encryption > > Engine (MEE) starting from the Skylake microacrhitecture. BIOS can define > > one or many MEE regions that can hold enclave data by configuring them with > > PRMRR registers. > > > > The MEE automatically encrypts the data leaving the processor package to > > the MEE regions. The data is encrypted using a random key whose life-time > > is exactly one power cycle. > > > > The current implementation requires that the firmware sets > > IA32_SGXLEPUBKEYHASH* MSRs as writable so that ultimately the kernel can > > decide what enclaves it wants run. The implementation does not create > > any bottlenecks to support read-only MSRs later on. > > > > You can tell if your CPU supports SGX by looking into /proc/cpuinfo: > > > > cat /proc/cpuinfo | grep sgx > > > > v20: > > * Fine-tune Kconfig messages and spacing and remove MMU_NOTIFIER > > dependency as MMU notifiers are no longer used in the driver. > > * Use mm_users instead of mm_count as refcount for mm_struct as mm_count > > only protects from deleting mm_struct, not removing its contents. > > * Sanitize EPC when the reclaimer thread starts by doing EREMOVE for all > > of them. They could be in initialized state when the kernel starts > > because it might be spawned by kexec(). > > * Documentation overhaul. > > * Use a device /dev/sgx/provision for delivering the provision token > > instead of securityfs. > > * Create a reference to the enclave when already when opening > > /dev/sgx/enclave. The file is then associated with this enclave only. > > mmap() can be done at free at any point and always get a reference to > > the enclave. To summarize the file now represents the enclave. > > > > v19: > > * Took 3-4 months but in some sense this was more like a rewrite of most > > of the corners of the source code. If I've forgotten to deal with some > > feedback, please don't shout me. Make a remark and I will fix it for > > the next version. Hopefully there won't be this big turnovers anymore. > > * Validate SECS attributes properly against CPUID given attributes and > > against allowed attributes. SECS attributes are the ones that are > > enforced whereas SIGSTRUCT attributes tell what is required to run > > the enclave. > > * Add KSS (Key Sharing Support) to the enclave attributes. > > * Deny MAP_PRIVATE as an enclave is always a shared memory entity. > > * Revert back to shmem backing storage so that it can be easily shared > > by multiple processes. > > * Split the recognization of an ENCLS leaf failure by using three > > functions to detect it: encsl_faulted(), encls_returned_code() and > > sgx_failed(). encls_failed() is only caused by a spurious expections that > > should never happen. Thus, it is not defined as an inline function in > > order to easily insert a kprobe to it. > > * Move low-level enclave management routines, page fault handler and page > > reclaiming routines from driver to the core. These cannot be separated > > from each other as they are heavily interdependent. The rationale is that > > the core does not call any code from the driver. > > * Allow the driver to be compiled as a module now that it no code is using > > its routines and it only uses exported symbols. Now the driver is > > essentially just a thin ioctl layer. > > * Reworked the driver to maintain a list of mm_struct's. The VMA callbacks > > add new entries to this list as the process is forked. Each entry has > > its own refcount because they have a different life-cycle as the enclave > > does. In effect @tgid and @mm have been removed from struct sgx_encl > > and we allow forking by removing VM_DONTCOPY from vm flags. > > * Generate a cpu mask in the reclaimer from the cpu mask's of all > > mm_struct's. This will kick out the hardware threads out of the enclave > > from multiple processes. It is not a local variable because it would > > eat too much of the stack space but instead a field in struct > > sgx_encl. > > * Allow forking i.e. remove VM_DONTCOPY. I did not change the API > > because the old API scaled to the workload that Andy described. The > > codebase is now mostly API independent i.e. changing the API is a > > small task. For me the proper trigger to chanage it is a as concrete > > as possible workload that cannot be fulfilled. I hope you understand > > my thinking here. I don't want to change anything w/o proper basis > > but I'm ready to change anything if there is a proper basis. I do > > not have any kind of attachment to any particular type of API. > > * Add Sean's vDSO ENCLS(EENTER) patches and update selftest to use the > > new vDSO. > > > > v18: > > * Update the ioctl-number.txt. > > * Move the driver under arch/x86. > > * Add SGX features (SGX, SGX1, SGX2) to the disabled-features.h. > > * Rename the selftest as test_sgx (previously sgx-selftest). > > * In order to enable process accounting, swap EPC pages and PCMD's to a VMA > > instead of shmem. > > * Allow only to initialize and run enclaves with a subset of > > {DEBUG, MODE64BIT} set. > > * Add SGX_IOC_ENCLAVE_SET_ATTRIBUTE to allow an enclave to have privileged > > attributes e.g. PROVISIONKEY. > > > > v17: > > * Add a simple selftest. > > * Fix a null pointer dereference to section->pages when its > > allocation fails. > > * Add Sean's description of the exception handling to the documentation. > > > > v16: > > * Fixed SOB's in the commits that were a bit corrupted in v15. > > * Implemented exceptio handling properly to detect_sgx(). > > * Use GENMASK() to define SGX_CPUID_SUB_LEAF_TYPE_MASK. > > * Updated the documentation to use rst definition lists. > > * Added the missing Documentation/x86/index.rst, which has a link to > > intel_sgx.rst. Now the SGX and uapi documentation is properly generated > > with 'make htmldocs'. > > * While enumerating EPC sections, if an undefined section is found, fail > > the driver initialization instead of continuing the initialization. > > * Issue a warning if there are more than %SGX_MAX_EPC_SECTIONS. > > * Remove copyright notice from arch/x86/include/asm/sgx.h. > > * Migrated from ioremap_cache() to memremap(). > > > > v15: > > * Split into more digestable size patches. > > * Lots of small fixes and clean ups. > > * Signal a "plain" SIGSEGV on an EPCM violation. > > > > v14: > > * Change the comment about X86_FEATURE_SGX_LC from “SGX launch > > configuration” to “SGX launch control”. > > * Move the SGX-related CPU feature flags as part of the Linux defined > > virtual leaf 8. > > * Add SGX_ prefix to the constants defining the ENCLS leaf functions. > > * Use GENMASK*() and BIT*() in sgx_arch.h instead of raw hex numbers. > > * Refine the long description for CONFIG_INTEL_SGX_CORE. > > * Do not use pr_*_ratelimited() in the driver. The use of the rate limited > > versions is legacy cruft from the prototyping phase. > > * Detect sleep with SGX_INVALID_EINIT_TOKEN instead of counting power > > cycles. > > * Manually prefix with “sgx:” in the core SGX code instead of redefining > > pr_fmt. > > * Report if IA32_SGXLEPUBKEYHASHx MSRs are not writable in the driver > > instead of core because it is a driver requirement. > > * Change prompt to bool in the entry for CONFIG_INTEL_SGX_CORE because the > > default is ‘n’. > > * Rename struct sgx_epc_bank as struct sgx_epc_section in order to match > > the SDM. > > * Allocate struct sgx_epc_page instances one at a time. > > * Use “__iomem void *” pointers for the mapped EPC memory consistently. > > * Retry once on SGX_INVALID_TOKEN in sgx_einit() instead of counting power > > cycles. > > * Call enclave swapping operations directly from the driver instead of > > calling them .indirectly through struct sgx_epc_page_ops because indirect > > calls are not required yet as the patch set does not contain the KVM > > support. > > * Added special signal SEGV_SGXERR to notify about SGX EPCM violation > > errors. > > > > v13: > > * Always use SGX_CPUID constant instead of a hardcoded value. > > * Simplified and documented the macros and functions for ENCLS leaves. > > * Enable sgx_free_page() to free active enclave pages on demand > > in order to allow sgx_invalidate() to delete enclave pages. > > It no longer performs EREMOVE if a page is in the process of > > being reclaimed. > > * Use PM notifier per enclave so that we don't have to traverse > > the global list of active EPC pages to find enclaves. > > * Removed unused SGX_LE_ROLLBACK constant from uapi/asm/sgx.h > > * Always use ioremap() to map EPC banks as we only support 64-bit kernel. > > * Invalidate IA32_SGXLEPUBKEYHASH cache used by sgx_einit() when going > > to sleep. > > > > v12: > > * Split to more narrow scoped commits in order to ease the review process and > > use co-developed-by tag for co-authors of commits instead of listing them in > > the source files. > > * Removed cruft EXPORT_SYMBOL() declarations and converted to static variables. > > * Removed in-kernel LE i.e. this version of the SGX software stack only > > supports unlocked IA32_SGXLEPUBKEYHASHx MSRs. > > * Refined documentation on launching enclaves, swapping and enclave > > construction. > > * Refined sgx_arch.h to include alignment information for every struct that > > requires it and removed structs that are not needed without an LE. > > * Got rid of SGX_CPUID. > > * SGX detection now prints log messages about firmware configuration issues. > > > > v11: > > * Polished ENCLS wrappers with refined exception handling. > > * ksgxswapd was not stopped (regression in v5) in > > sgx_page_cache_teardown(), which causes a leaked kthread after driver > > deinitialization. > > * Shutdown sgx_le_proxy when going to suspend because its EPC pages will be > > invalidated when resuming, which will cause it not function properly > > anymore. > > * Set EINITTOKEN.VALID to zero for a token that is passed when > > SGXLEPUBKEYHASH matches MRSIGNER as alloc_page() does not give a zero > > page. > > * Fixed the check in sgx_edbgrd() for a TCS page. Allowed to read offsets > > around the flags field, which causes a #GP. Only flags read is readable. > > * On read access memcpy() call inside sgx_vma_access() had src and dest > > parameters in wrong order. > > * The build issue with CONFIG_KASAN is now fixed. Added undefined symbols > > to LE even if “KASAN_SANITIZE := false” was set in the makefile. > > * Fixed a regression in the #PF handler. If a page has > > SGX_ENCL_PAGE_RESERVED flag the #PF handler should unconditionally fail. > > It did not, which caused weird races when trying to change other parts of > > swapping code. > > * EPC management has been refactored to a flat LRU cache and moved to > > arch/x86. The swapper thread reads a cluster of EPC pages and swaps all > > of them. It can now swap from multiple enclaves in the same round. > > * For the sake of consistency with SGX_IOC_ENCLAVE_ADD_PAGE, return -EINVAL > > when an enclave is already initialized or dead instead of zero. > > > > v10: > > * Cleaned up anon inode based IPC between the ring-0 and ring-3 parts > > of the driver. > > * Unset the reserved flag from an enclave page if EDBGRD/WR fails > > (regression in v6). > > * Close the anon inode when LE is stopped (regression in v9). > > * Update the documentation with a more detailed description of SGX. > > > > v9: > > * Replaced kernel-LE IPC based on pipes with an anonymous inode. > > The driver does not require anymore new exports. > > > > v8: > > * Check that public key MSRs match the LE public key hash in the > > driver initialization when the MSRs are read-only. > > * Fix the race in VA slot allocation by checking the fullness > > immediately after succeesful allocation. > > * Fix the race in hash mrsigner calculation between the launch > > enclave and user enclaves by having a separate lock for hash > > calculation. > > > > v7: > > * Fixed offset calculation in sgx_edbgr/wr(). Address was masked with PAGE_MASK > > when it should have been masked with ~PAGE_MASK. > > * Fixed a memory leak in sgx_ioc_enclave_create(). > > * Simplified swapping code by using a pointer array for a cluster > > instead of a linked list. > > * Squeezed struct sgx_encl_page to 32 bytes. > > * Fixed deferencing of an RSA key on OpenSSL 1.1.0. > > * Modified TC's CMAC to use kernel AES-NI. Restructured the code > > a bit in order to better align with kernel conventions. > > > > v6: > > * Fixed semaphore underrun when accessing /dev/sgx from the launch enclave. > > * In sgx_encl_create() s/IS_ERR(secs)/IS_ERR(encl)/. > > * Removed virtualization chapter from the documentation. > > * Changed the default filename for the signing key as signing_key.pem. > > * Reworked EPC management in a way that instead of a linked list of > > struct sgx_epc_page instances there is an array of integers that > > encodes address and bank of an EPC page (the same data as 'pa' field > > earlier). The locking has been moved to the EPC bank level instead > > of a global lock. > > * Relaxed locking requirements for EPC management. EPC pages can be > > released back to the EPC bank concurrently. > > * Cleaned up ptrace() code. > > * Refined commit messages for new architectural constants. > > * Sorted includes in every source file. > > * Sorted local variable declarations according to the line length in > > every function. > > * Style fixes based on Darren's comments to sgx_le.c. > > > > v5: > > * Described IPC between the Launch Enclave and kernel in the commit messages. > > * Fixed all relevant checkpatch.pl issues that I have forgot fix in earlier > > versions except those that exist in the imported TinyCrypt code. > > * Fixed spelling mistakes in the documentation. > > * Forgot to check the return value of sgx_drv_subsys_init(). > > * Encapsulated properly page cache init and teardown. > > * Collect epc pages to a temp list in sgx_add_epc_bank > > * Removed SGX_ENCLAVE_INIT_ARCH constant. > > > > v4: > > * Tied life-cycle of the sgx_le_proxy process to /dev/sgx. > > * Removed __exit annotation from sgx_drv_subsys_exit(). > > * Fixed a leak of a backing page in sgx_process_add_page_req() in the > > case when vm_insert_pfn() fails. > > * Removed unused symbol exports for sgx_page_cache.c. > > * Updated sgx_alloc_page() to require encl parameter and documented the > > behavior (Sean Christopherson). > > * Refactored a more lean API for sgx_encl_find() and documented the behavior. > > * Moved #PF handler to sgx_fault.c. > > * Replaced subsys_system_register() with plain bus_register(). > > * Retry EINIT 2nd time only if MSRs are not locked. > > > > v3: > > * Check that FEATURE_CONTROL_LOCKED and FEATURE_CONTROL_SGX_ENABLE are set. > > * Return -ERESTARTSYS in __sgx_encl_add_page() when sgx_alloc_page() fails. > > * Use unused bits in epc_page->pa to store the bank number. > > * Removed #ifdef for WQ_NONREENTRANT. > > * If mmu_notifier_register() fails with -EINTR, return -ERESTARTSYS. > > * Added --remove-section=.got.plt to objcopy flags in order to prevent a > > dummy .got.plt, which will cause an inconsistent size for the LE. > > * Documented sgx_encl_* functions. > > * Added remark about AES implementation used inside the LE. > > * Removed redundant sgx_sys_exit() from le/main.c. > > * Fixed struct sgx_secinfo alignment from 128 to 64 bytes. > > * Validate miscselect in sgx_encl_create(). > > * Fixed SSA frame size calculation to take the misc region into account. > > * Implemented consistent exception handling to __encls() and __encls_ret(). > > * Implemented a proper device model in order to allow sysfs attributes > > and in-kernel API. > > * Cleaned up various "find enclave" implementations to the unified > > sgx_encl_find(). > > * Validate that vm_pgoff is zero. > > * Discard backing pages with shmem_truncate_range() after EADD. > > * Added missing EEXTEND operations to LE signing and launch. > > * Fixed SSA size for GPRS region from 168 to 184 bytes. > > * Fixed the checks for TCS flags. Now DBGOPTIN is allowed. > > * Check that TCS addresses are in ELRANGE and not just page aligned. > > * Require kernel to be compiled with X64_64 and CPU_SUP_INTEL. > > * Fixed an incorrect value for SGX_ATTR_DEBUG from 0x01 to 0x02. > > > > v2: > > * get_rand_uint32() changed the value of the pointer instead of value > > where it is pointing at. > > * Launch enclave incorrectly used sigstruct attributes-field instead of > > enclave attributes-field. > > * Removed unused struct sgx_add_page_req from sgx_ioctl.c > > * Removed unused sgx_has_sgx2. > > * Updated arch/x86/include/asm/sgx.h so that it provides stub > > implementations when sgx in not enabled. > > * Removed cruft rdmsr-calls from sgx_set_pubkeyhash_msrs(). > > * return -ENOMEM in sgx_alloc_page() when VA pages consume too much space > > * removed unused global sgx_nr_pids > > * moved sgx_encl_release to sgx_encl.c > > * return -ERESTARTSYS instead of -EINTR in sgx_encl_init() > > > > Jarkko Sakkinen (11): > > x86/sgx: Add ENCLS architectural error codes > > x86/sgx: Add SGX1 and SGX2 architectural data structures > > x86/sgx: Add wrappers for ENCLS leaf functions > > x86/sgx: Add functions to allocate and free EPC pages > > x86/sgx: Add the Linux SGX Enclave Driver > > x86/sgx: Add provisioning > > x86/sgx: Add swapping code to the core and SGX driver > > x86/sgx: ptrace() support for the SGX driver > > selftests/x86: Add a selftest for SGX > > x86/sgx: Update MAINTAINERS > > docs: x86/sgx: Document the enclave API > > > > Kai Huang (2): > > x86/cpufeatures: Add Intel-defined SGX feature bit > > x86/cpufeatures: Add Intel-defined SGX_LC feature bit > > > > Sean Christopherson (15): > > x86/cpufeatures: Add SGX sub-features (as Linux-defined bits) > > x86/msr: Add IA32_FEATURE_CONTROL.SGX_ENABLE definition > > x86/msr: Add SGX Launch Control MSR definitions > > x86/mm: x86/sgx: Add new 'PF_SGX' page fault error code bit > > x86/mm: x86/sgx: Signal SIGSEGV for userspace #PFs w/ PF_SGX > > x86/cpu/intel: Detect SGX support and update caps appropriately > > x86/sgx: Enumerate and track EPC sections > > x86/sgx: Add sgx_einit() for initializing enclaves > > x86/vdso: Add support for exception fixup in vDSO functions > > x86/fault: Add helper function to sanitize error code > > x86/fault: Attempt to fixup unhandled #PF in vDSO before signaling > > x86/traps: Attempt to fixup exceptions in vDSO before signaling > > x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave > > transitions > > docs: x86/sgx: Add Architecture documentation > > docs: x86/sgx: Document kernel internals > > > > Documentation/index.rst | 1 + > > Documentation/ioctl/ioctl-number.txt | 1 + > > Documentation/x86/index.rst | 10 + > > Documentation/x86/sgx/1.Architecture.rst | 431 +++++++++ > > Documentation/x86/sgx/2.Kernel-internals.rst | 56 ++ > > Documentation/x86/sgx/3.API.rst | 27 + > > Documentation/x86/sgx/index.rst | 18 + > > MAINTAINERS | 12 + > > arch/x86/Kconfig | 27 + > > arch/x86/entry/vdso/Makefile | 6 +- > > arch/x86/entry/vdso/extable.c | 37 + > > arch/x86/entry/vdso/extable.h | 29 + > > arch/x86/entry/vdso/vdso-layout.lds.S | 9 +- > > arch/x86/entry/vdso/vdso.lds.S | 1 + > > arch/x86/entry/vdso/vdso2c.h | 58 +- > > arch/x86/entry/vdso/vsgx_enter_enclave.S | 101 +++ > > arch/x86/include/asm/cpufeatures.h | 24 +- > > arch/x86/include/asm/disabled-features.h | 14 +- > > arch/x86/include/asm/msr-index.h | 8 + > > arch/x86/include/asm/traps.h | 1 + > > arch/x86/include/asm/vdso.h | 5 + > > arch/x86/include/uapi/asm/sgx.h | 86 ++ > > arch/x86/include/uapi/asm/sgx_errno.h | 91 ++ > > arch/x86/kernel/cpu/Makefile | 1 + > > arch/x86/kernel/cpu/intel.c | 39 + > > arch/x86/kernel/cpu/scattered.c | 2 + > > arch/x86/kernel/cpu/sgx/Makefile | 2 + > > arch/x86/kernel/cpu/sgx/arch.h | 424 +++++++++ > > arch/x86/kernel/cpu/sgx/driver/Makefile | 3 + > > arch/x86/kernel/cpu/sgx/driver/driver.h | 38 + > > arch/x86/kernel/cpu/sgx/driver/ioctl.c | 850 ++++++++++++++++++ > > arch/x86/kernel/cpu/sgx/driver/main.c | 368 ++++++++ > > arch/x86/kernel/cpu/sgx/encl.c | 709 +++++++++++++++ > > arch/x86/kernel/cpu/sgx/encl.h | 136 +++ > > arch/x86/kernel/cpu/sgx/encls.c | 22 + > > arch/x86/kernel/cpu/sgx/encls.h | 244 +++++ > > arch/x86/kernel/cpu/sgx/main.c | 360 ++++++++ > > arch/x86/kernel/cpu/sgx/reclaim.c | 482 ++++++++++ > > arch/x86/kernel/cpu/sgx/sgx.h | 90 ++ > > arch/x86/kernel/traps.c | 14 + > > arch/x86/mm/fault.c | 44 +- > > tools/arch/x86/include/asm/cpufeatures.h | 21 +- > > tools/testing/selftests/x86/Makefile | 10 + > > tools/testing/selftests/x86/sgx/Makefile | 48 + > > tools/testing/selftests/x86/sgx/defines.h | 39 + > > tools/testing/selftests/x86/sgx/encl.c | 20 + > > tools/testing/selftests/x86/sgx/encl.lds | 33 + > > .../selftests/x86/sgx/encl_bootstrap.S | 94 ++ > > tools/testing/selftests/x86/sgx/encl_piggy.S | 18 + > > tools/testing/selftests/x86/sgx/encl_piggy.h | 14 + > > tools/testing/selftests/x86/sgx/main.c | 279 ++++++ > > tools/testing/selftests/x86/sgx/sgx_call.S | 15 + > > tools/testing/selftests/x86/sgx/sgxsign.c | 508 +++++++++++ > > .../testing/selftests/x86/sgx/signing_key.pem | 39 + > > 54 files changed, 5987 insertions(+), 32 deletions(-) > > create mode 100644 Documentation/x86/index.rst > > create mode 100644 Documentation/x86/sgx/1.Architecture.rst > > create mode 100644 Documentation/x86/sgx/2.Kernel-internals.rst > > create mode 100644 Documentation/x86/sgx/3.API.rst > > create mode 100644 Documentation/x86/sgx/index.rst > > create mode 100644 arch/x86/entry/vdso/extable.c > > create mode 100644 arch/x86/entry/vdso/extable.h > > create mode 100644 arch/x86/entry/vdso/vsgx_enter_enclave.S > > create mode 100644 arch/x86/include/uapi/asm/sgx.h > > create mode 100644 arch/x86/include/uapi/asm/sgx_errno.h > > create mode 100644 arch/x86/kernel/cpu/sgx/Makefile > > create mode 100644 arch/x86/kernel/cpu/sgx/arch.h > > create mode 100644 arch/x86/kernel/cpu/sgx/driver/Makefile > > create mode 100644 arch/x86/kernel/cpu/sgx/driver/driver.h > > create mode 100644 arch/x86/kernel/cpu/sgx/driver/ioctl.c > > create mode 100644 arch/x86/kernel/cpu/sgx/driver/main.c > > create mode 100644 arch/x86/kernel/cpu/sgx/encl.c > > create mode 100644 arch/x86/kernel/cpu/sgx/encl.h > > create mode 100644 arch/x86/kernel/cpu/sgx/encls.c > > create mode 100644 arch/x86/kernel/cpu/sgx/encls.h > > create mode 100644 arch/x86/kernel/cpu/sgx/main.c > > create mode 100644 arch/x86/kernel/cpu/sgx/reclaim.c > > create mode 100644 arch/x86/kernel/cpu/sgx/sgx.h > > create mode 100644 tools/testing/selftests/x86/sgx/Makefile > > create mode 100644 tools/testing/selftests/x86/sgx/defines.h > > create mode 100644 tools/testing/selftests/x86/sgx/encl.c > > create mode 100644 tools/testing/selftests/x86/sgx/encl.lds > > create mode 100644 tools/testing/selftests/x86/sgx/encl_bootstrap.S > > create mode 100644 tools/testing/selftests/x86/sgx/encl_piggy.S > > create mode 100644 tools/testing/selftests/x86/sgx/encl_piggy.h > > create mode 100644 tools/testing/selftests/x86/sgx/main.c > > create mode 100644 tools/testing/selftests/x86/sgx/sgx_call.S > > create mode 100644 tools/testing/selftests/x86/sgx/sgxsign.c > > create mode 100644 tools/testing/selftests/x86/sgx/signing_key.pem > > > > -- > > 2.19.1 > > > > I'm on leave for this week and next week's Monday if you wonder why I'm > so passive in the discussion. Looking at the things next week's Tue. > > Just a quick comment about Andy's proposal. Probably pretty DSO like > ELF blob could work with an addition of a section called ".tcs" for > entry points. They need to be recognized so that the loader can add > them as TCS pages. Hmm. There's a decent argument for specifically supporting whatever format Windows uses. There's also an argument for allowing one or more enclaves to be bundled in a regular ELF DSO. FWIW, there's no fundamental reason we can't support more than one type of enclave format.
On 2019-04-22 09:26, Andy Lutomirski wrote: >> On Apr 19, 2019, at 2:56 PM, Jethro Beekman <jethro@fortanix.com> wrote: >> This works fine with v20 as-is. However, consider the equivalent of the >> PT-based flow: >> >> mmap(PROT_READ|PROT_EXEC) >> ioctl(EADD) <-- no error! > > Indeed! > >> >> It's not me that's working around the LSM, it's the SGX driver! It's >> writing to memory that's not marked writable! The fundamental issue here >> is that the SGX instruction set has several instructions that bypass the >> page table permission bits, and this is (naturally) confusing to any >> kind of reference monitor like the LSM framework. You can come up with >> similar scenarios that involve PROT_READ|PROT_WRITE|PROT_EXEC or >> ptrace(PTRACE_POKETEXT). So, clearly, the proper way to fix this failure >> of complete mediation is by enforcing appropriate page-table permissions >> even on the SGX instructions that don't do it themselves. Just make any >> implicit memory access look like a regular memory access and now >> everyone is on the same page (pun intended). >> > > I agree that we should do this. But then what? Then, we have a minimum viable SGX implementation that doesn't make things worse than they are today from a userspace code loading/LSM perspective. People without LSMs can use SGX and people with LSMs are not more vulnerable than before. I agree that we should do something along the following lines... > So I think we need a better EADD ioctl that explicitly does work on > PROT_READ|PROT_EXEC enclave memory but makes up for by validating the > *source* of the data. The effect will be similar to mapping a > labeled, appraised, etc file as PROT_EXEC. ... but I don't see why this would need to be in the initial patch set. We need to take some time to explore the design space here (see additional comments below), and I don't think it's necessary to wait for it. > Maybe, in extreme pseudocode: > > fd = open(“/dev/sgx/enclave”); > ioctl(fd, SGX_CREATE_FROM_FILE, file_fd); > // fd now inherits the LSM label from the file, or is otherwise marked. > mmap(fd, PROT_READ|PROT_EXEC, ...); > > I suppose that an alternative would be to delegate all the EADD calls > to a privileged daemon, but that’s nasty. > What file format should this be in? I have worked with several different binary enclave formats and I don't really like any of them. # ELF Pros: * People know about ELF. * Allows storing additional metadata that is only used by userspace, not the enclave itself. Cons: * ELF generally loads all kinds of stuff in memory that is not necessary for enclaves, such as the ELF header. * Special tools are needed to calculate ENCLAVEHASH, for signing & verification. * All tools need to agree on the exact transformation. * Unclear how to specify things such as: which 256-byte chunks of memory should be measured, heap, TCS pages, stacks, SSAs, etc. Questions: * If using ELF, should this be the same format that the Intel Linux SDK uses (not documented, but source code is available) or something newly standardized? # PE (Windows Intel SDK format) Andy suggested this in another email. I'm not sure why exactly? Pros: * Used by Windows enclaves? * Allows storing additional metadata that is only used by userspace, not the enclave itself. Cons: * The format is not documented. I did some RE on this format a long time ago. See https://github.com/fortanix/rust-sgx/blob/master/doc/WINTEL-SGX-ABI.md and https://github.com/fortanix/rust-sgx/blob/master/sgxs-tools/src/bin/isgx-pe2sgx.rs. * PE is not really used on Linux. * All same cons as ELF above. # CPU-native (hash stream) "SGXS" The security properties of an enclave are completely defined by the hash that's calculated by the processor while loading the enclave. The exact hashed data is what I call the "SGX stream" format (SGXS). This is fully described by the Intel SDM. I've written down some notes about this at https://github.com/fortanix/rust-sgx/blob/master/doc/SGXS.md. That document also defines a notion of canonicality for streams. You can ignore the section on "Enhanced SGXS", which is a failed experiment. Pros: * Computing ENCLAVEHASH is a simple SHA256 of the file. * No complex transformations needed to load enclave. Cons: * No way to specify memory contents of non-measured memory. * No space for non-enclave metadata (including SIGSTRUCT). * Not a standard format for transporting binaries. # CPU-native (instruction stream) An enclave's memory contents is fully defined by the set of ECREATE/EADD/EEXTEND/EINIT instructions that the OS needs to execute. One could envision a format that describes exactly those instructions. One difference with the SGXS format described above is that the enclave memory is described as part of EADD, not EEXTEND. This allows including specific values for non-measured memory. Pros: * No complex transformations needed to load enclave. * Obvious place to store SIGSTRUCT. Cons: * Special tools are needed to calculate ENCLAVEHASH, for signing & verification. * No obvious space for non-enclave metadata. * Not a standard format for transporting binaries. --- We've been using the SGXS format for a couple of years, and also the "Enhanced SGXS" format. I think SGXS make a lot of sense for SGX software, Enhanced SGXS not so much. I've recently been pondering developing a new format that is basically an archive (tar? but preferably something with an index) of SGXS, SIGSTRUCT, some file describing non-measured memory contents (format TBD), and additional non-enclave metadata. I'd be interested in hearing people's thoughts on file formats. -- Jethro Beekman | Fortanix
On 2019-04-23 19:52, Andy Lutomirski wrote: > On Tue, Apr 23, 2019 at 4:56 AM Jarkko Sakkinen > <jarkko.sakkinen@linux.intel.com> wrote: >> >> On Wed, Apr 17, 2019 at 01:39:10PM +0300, Jarkko Sakkinen wrote: >> > Intel(R) SGX is a set of CPU instructions that can be used by applications >> > to set aside private regions of code and data. The code outside the enclave >> > is disallowed to access the memory inside the enclave by the CPU access >> > control. In a way you can think that SGX provides inverted sandbox. It >> > protects the application from a malicious host. >> > >> > There is a new hardware unit in the processor called Memory Encryption >> > Engine (MEE) starting from the Skylake microacrhitecture. BIOS can define >> > one or many MEE regions that can hold enclave data by configuring them with >> > PRMRR registers. >> > >> > The MEE automatically encrypts the data leaving the processor package to >> > the MEE regions. The data is encrypted using a random key whose life-time >> > is exactly one power cycle. >> > >> > The current implementation requires that the firmware sets >> > IA32_SGXLEPUBKEYHASH* MSRs as writable so that ultimately the kernel can >> > decide what enclaves it wants run. The implementation does not create >> > any bottlenecks to support read-only MSRs later on. >> > >> > You can tell if your CPU supports SGX by looking into /proc/cpuinfo: >> > >> > cat /proc/cpuinfo | grep sgx >> > >> > v20: >> > * Fine-tune Kconfig messages and spacing and remove MMU_NOTIFIER >> > dependency as MMU notifiers are no longer used in the driver. >> > * Use mm_users instead of mm_count as refcount for mm_struct as mm_count >> > only protects from deleting mm_struct, not removing its contents. >> > * Sanitize EPC when the reclaimer thread starts by doing EREMOVE for all >> > of them. They could be in initialized state when the kernel starts >> > because it might be spawned by kexec(). >> > * Documentation overhaul. >> > * Use a device /dev/sgx/provision for delivering the provision token >> > instead of securityfs. >> > * Create a reference to the enclave when already when opening >> > /dev/sgx/enclave. The file is then associated with this enclave only. >> > mmap() can be done at free at any point and always get a reference to >> > the enclave. To summarize the file now represents the enclave. >> > >> > v19: >> > * Took 3-4 months but in some sense this was more like a rewrite of most >> > of the corners of the source code. If I've forgotten to deal with some >> > feedback, please don't shout me. Make a remark and I will fix it for >> > the next version. Hopefully there won't be this big turnovers anymore. >> > * Validate SECS attributes properly against CPUID given attributes and >> > against allowed attributes. SECS attributes are the ones that are >> > enforced whereas SIGSTRUCT attributes tell what is required to run >> > the enclave. >> > * Add KSS (Key Sharing Support) to the enclave attributes. >> > * Deny MAP_PRIVATE as an enclave is always a shared memory entity. >> > * Revert back to shmem backing storage so that it can be easily shared >> > by multiple processes. >> > * Split the recognization of an ENCLS leaf failure by using three >> > functions to detect it: encsl_faulted(), encls_returned_code() and >> > sgx_failed(). encls_failed() is only caused by a spurious expections that >> > should never happen. Thus, it is not defined as an inline function in >> > order to easily insert a kprobe to it. >> > * Move low-level enclave management routines, page fault handler and page >> > reclaiming routines from driver to the core. These cannot be separated >> > from each other as they are heavily interdependent. The rationale is that >> > the core does not call any code from the driver. >> > * Allow the driver to be compiled as a module now that it no code is using >> > its routines and it only uses exported symbols. Now the driver is >> > essentially just a thin ioctl layer. >> > * Reworked the driver to maintain a list of mm_struct's. The VMA callbacks >> > add new entries to this list as the process is forked. Each entry has >> > its own refcount because they have a different life-cycle as the enclave >> > does. In effect @tgid and @mm have been removed from struct sgx_encl >> > and we allow forking by removing VM_DONTCOPY from vm flags. >> > * Generate a cpu mask in the reclaimer from the cpu mask's of all >> > mm_struct's. This will kick out the hardware threads out of the enclave >> > from multiple processes. It is not a local variable because it would >> > eat too much of the stack space but instead a field in struct >> > sgx_encl. >> > * Allow forking i.e. remove VM_DONTCOPY. I did not change the API >> > because the old API scaled to the workload that Andy described. The >> > codebase is now mostly API independent i.e. changing the API is a >> > small task. For me the proper trigger to chanage it is a as concrete >> > as possible workload that cannot be fulfilled. I hope you understand >> > my thinking here. I don't want to change anything w/o proper basis >> > but I'm ready to change anything if there is a proper basis. I do >> > not have any kind of attachment to any particular type of API. >> > * Add Sean's vDSO ENCLS(EENTER) patches and update selftest to use the >> > new vDSO. >> > >> > v18: >> > * Update the ioctl-number.txt. >> > * Move the driver under arch/x86. >> > * Add SGX features (SGX, SGX1, SGX2) to the disabled-features.h. >> > * Rename the selftest as test_sgx (previously sgx-selftest). >> > * In order to enable process accounting, swap EPC pages and PCMD's to a VMA >> > instead of shmem. >> > * Allow only to initialize and run enclaves with a subset of >> > {DEBUG, MODE64BIT} set. >> > * Add SGX_IOC_ENCLAVE_SET_ATTRIBUTE to allow an enclave to have privileged >> > attributes e.g. PROVISIONKEY. >> > >> > v17: >> > * Add a simple selftest. >> > * Fix a null pointer dereference to section->pages when its >> > allocation fails. >> > * Add Sean's description of the exception handling to the documentation. >> > >> > v16: >> > * Fixed SOB's in the commits that were a bit corrupted in v15. >> > * Implemented exceptio handling properly to detect_sgx(). >> > * Use GENMASK() to define SGX_CPUID_SUB_LEAF_TYPE_MASK. >> > * Updated the documentation to use rst definition lists. >> > * Added the missing Documentation/x86/index.rst, which has a link to >> > intel_sgx.rst. Now the SGX and uapi documentation is properly generated >> > with 'make htmldocs'. >> > * While enumerating EPC sections, if an undefined section is found, fail >> > the driver initialization instead of continuing the initialization. >> > * Issue a warning if there are more than %SGX_MAX_EPC_SECTIONS. >> > * Remove copyright notice from arch/x86/include/asm/sgx.h. >> > * Migrated from ioremap_cache() to memremap(). >> > >> > v15: >> > * Split into more digestable size patches. >> > * Lots of small fixes and clean ups. >> > * Signal a "plain" SIGSEGV on an EPCM violation. >> > >> > v14: >> > * Change the comment about X86_FEATURE_SGX_LC from “SGX launch >> > configuration” to “SGX launch control”. >> > * Move the SGX-related CPU feature flags as part of the Linux defined >> > virtual leaf 8. >> > * Add SGX_ prefix to the constants defining the ENCLS leaf functions. >> > * Use GENMASK*() and BIT*() in sgx_arch.h instead of raw hex numbers. >> > * Refine the long description for CONFIG_INTEL_SGX_CORE. >> > * Do not use pr_*_ratelimited() in the driver. The use of the rate limited >> > versions is legacy cruft from the prototyping phase. >> > * Detect sleep with SGX_INVALID_EINIT_TOKEN instead of counting power >> > cycles. >> > * Manually prefix with “sgx:” in the core SGX code instead of redefining >> > pr_fmt. >> > * Report if IA32_SGXLEPUBKEYHASHx MSRs are not writable in the driver >> > instead of core because it is a driver requirement. >> > * Change prompt to bool in the entry for CONFIG_INTEL_SGX_CORE because the >> > default is ‘n’. >> > * Rename struct sgx_epc_bank as struct sgx_epc_section in order to match >> > the SDM. >> > * Allocate struct sgx_epc_page instances one at a time. >> > * Use “__iomem void *” pointers for the mapped EPC memory consistently. >> > * Retry once on SGX_INVALID_TOKEN in sgx_einit() instead of counting power >> > cycles. >> > * Call enclave swapping operations directly from the driver instead of >> > calling them .indirectly through struct sgx_epc_page_ops because indirect >> > calls are not required yet as the patch set does not contain the KVM >> > support. >> > * Added special signal SEGV_SGXERR to notify about SGX EPCM violation >> > errors. >> > >> > v13: >> > * Always use SGX_CPUID constant instead of a hardcoded value. >> > * Simplified and documented the macros and functions for ENCLS leaves. >> > * Enable sgx_free_page() to free active enclave pages on demand >> > in order to allow sgx_invalidate() to delete enclave pages. >> > It no longer performs EREMOVE if a page is in the process of >> > being reclaimed. >> > * Use PM notifier per enclave so that we don't have to traverse >> > the global list of active EPC pages to find enclaves. >> > * Removed unused SGX_LE_ROLLBACK constant from uapi/asm/sgx.h >> > * Always use ioremap() to map EPC banks as we only support 64-bit kernel. >> > * Invalidate IA32_SGXLEPUBKEYHASH cache used by sgx_einit() when going >> > to sleep. >> > >> > v12: >> > * Split to more narrow scoped commits in order to ease the review process and >> > use co-developed-by tag for co-authors of commits instead of listing them in >> > the source files. >> > * Removed cruft EXPORT_SYMBOL() declarations and converted to static variables. >> > * Removed in-kernel LE i.e. this version of the SGX software stack only >> > supports unlocked IA32_SGXLEPUBKEYHASHx MSRs. >> > * Refined documentation on launching enclaves, swapping and enclave >> > construction. >> > * Refined sgx_arch.h to include alignment information for every struct that >> > requires it and removed structs that are not needed without an LE. >> > * Got rid of SGX_CPUID. >> > * SGX detection now prints log messages about firmware configuration issues. >> > >> > v11: >> > * Polished ENCLS wrappers with refined exception handling. >> > * ksgxswapd was not stopped (regression in v5) in >> > sgx_page_cache_teardown(), which causes a leaked kthread after driver >> > deinitialization. >> > * Shutdown sgx_le_proxy when going to suspend because its EPC pages will be >> > invalidated when resuming, which will cause it not function properly >> > anymore. >> > * Set EINITTOKEN.VALID to zero for a token that is passed when >> > SGXLEPUBKEYHASH matches MRSIGNER as alloc_page() does not give a zero >> > page. >> > * Fixed the check in sgx_edbgrd() for a TCS page. Allowed to read offsets >> > around the flags field, which causes a #GP. Only flags read is readable. >> > * On read access memcpy() call inside sgx_vma_access() had src and dest >> > parameters in wrong order. >> > * The build issue with CONFIG_KASAN is now fixed. Added undefined symbols >> > to LE even if “KASAN_SANITIZE := false” was set in the makefile. >> > * Fixed a regression in the #PF handler. If a page has >> > SGX_ENCL_PAGE_RESERVED flag the #PF handler should unconditionally fail. >> > It did not, which caused weird races when trying to change other parts of >> > swapping code. >> > * EPC management has been refactored to a flat LRU cache and moved to >> > arch/x86. The swapper thread reads a cluster of EPC pages and swaps all >> > of them. It can now swap from multiple enclaves in the same round. >> > * For the sake of consistency with SGX_IOC_ENCLAVE_ADD_PAGE, return -EINVAL >> > when an enclave is already initialized or dead instead of zero. >> > >> > v10: >> > * Cleaned up anon inode based IPC between the ring-0 and ring-3 parts >> > of the driver. >> > * Unset the reserved flag from an enclave page if EDBGRD/WR fails >> > (regression in v6). >> > * Close the anon inode when LE is stopped (regression in v9). >> > * Update the documentation with a more detailed description of SGX. >> > >> > v9: >> > * Replaced kernel-LE IPC based on pipes with an anonymous inode. >> > The driver does not require anymore new exports. >> > >> > v8: >> > * Check that public key MSRs match the LE public key hash in the >> > driver initialization when the MSRs are read-only. >> > * Fix the race in VA slot allocation by checking the fullness >> > immediately after succeesful allocation. >> > * Fix the race in hash mrsigner calculation between the launch >> > enclave and user enclaves by having a separate lock for hash >> > calculation. >> > >> > v7: >> > * Fixed offset calculation in sgx_edbgr/wr(). Address was masked with PAGE_MASK >> > when it should have been masked with ~PAGE_MASK. >> > * Fixed a memory leak in sgx_ioc_enclave_create(). >> > * Simplified swapping code by using a pointer array for a cluster >> > instead of a linked list. >> > * Squeezed struct sgx_encl_page to 32 bytes. >> > * Fixed deferencing of an RSA key on OpenSSL 1.1.0. >> > * Modified TC's CMAC to use kernel AES-NI. Restructured the code >> > a bit in order to better align with kernel conventions. >> > >> > v6: >> > * Fixed semaphore underrun when accessing /dev/sgx from the launch enclave. >> > * In sgx_encl_create() s/IS_ERR(secs)/IS_ERR(encl)/. >> > * Removed virtualization chapter from the documentation. >> > * Changed the default filename for the signing key as signing_key.pem. >> > * Reworked EPC management in a way that instead of a linked list of >> > struct sgx_epc_page instances there is an array of integers that >> > encodes address and bank of an EPC page (the same data as 'pa' field >> > earlier). The locking has been moved to the EPC bank level instead >> > of a global lock. >> > * Relaxed locking requirements for EPC management. EPC pages can be >> > released back to the EPC bank concurrently. >> > * Cleaned up ptrace() code. >> > * Refined commit messages for new architectural constants. >> > * Sorted includes in every source file. >> > * Sorted local variable declarations according to the line length in >> > every function. >> > * Style fixes based on Darren's comments to sgx_le.c. >> > >> > v5: >> > * Described IPC between the Launch Enclave and kernel in the commit messages. >> > * Fixed all relevant checkpatch.pl issues that I have forgot fix in earlier >> > versions except those that exist in the imported TinyCrypt code. >> > * Fixed spelling mistakes in the documentation. >> > * Forgot to check the return value of sgx_drv_subsys_init(). >> > * Encapsulated properly page cache init and teardown. >> > * Collect epc pages to a temp list in sgx_add_epc_bank >> > * Removed SGX_ENCLAVE_INIT_ARCH constant. >> > >> > v4: >> > * Tied life-cycle of the sgx_le_proxy process to /dev/sgx. >> > * Removed __exit annotation from sgx_drv_subsys_exit(). >> > * Fixed a leak of a backing page in sgx_process_add_page_req() in the >> > case when vm_insert_pfn() fails. >> > * Removed unused symbol exports for sgx_page_cache.c. >> > * Updated sgx_alloc_page() to require encl parameter and documented the >> > behavior (Sean Christopherson). >> > * Refactored a more lean API for sgx_encl_find() and documented the behavior. >> > * Moved #PF handler to sgx_fault.c. >> > * Replaced subsys_system_register() with plain bus_register(). >> > * Retry EINIT 2nd time only if MSRs are not locked. >> > >> > v3: >> > * Check that FEATURE_CONTROL_LOCKED and FEATURE_CONTROL_SGX_ENABLE are set. >> > * Return -ERESTARTSYS in __sgx_encl_add_page() when sgx_alloc_page() fails. >> > * Use unused bits in epc_page->pa to store the bank number. >> > * Removed #ifdef for WQ_NONREENTRANT. >> > * If mmu_notifier_register() fails with -EINTR, return -ERESTARTSYS. >> > * Added --remove-section=.got.plt to objcopy flags in order to prevent a >> > dummy .got.plt, which will cause an inconsistent size for the LE. >> > * Documented sgx_encl_* functions. >> > * Added remark about AES implementation used inside the LE. >> > * Removed redundant sgx_sys_exit() from le/main.c. >> > * Fixed struct sgx_secinfo alignment from 128 to 64 bytes. >> > * Validate miscselect in sgx_encl_create(). >> > * Fixed SSA frame size calculation to take the misc region into account. >> > * Implemented consistent exception handling to __encls() and __encls_ret(). >> > * Implemented a proper device model in order to allow sysfs attributes >> > and in-kernel API. >> > * Cleaned up various "find enclave" implementations to the unified >> > sgx_encl_find(). >> > * Validate that vm_pgoff is zero. >> > * Discard backing pages with shmem_truncate_range() after EADD. >> > * Added missing EEXTEND operations to LE signing and launch. >> > * Fixed SSA size for GPRS region from 168 to 184 bytes. >> > * Fixed the checks for TCS flags. Now DBGOPTIN is allowed. >> > * Check that TCS addresses are in ELRANGE and not just page aligned. >> > * Require kernel to be compiled with X64_64 and CPU_SUP_INTEL. >> > * Fixed an incorrect value for SGX_ATTR_DEBUG from 0x01 to 0x02. >> > >> > v2: >> > * get_rand_uint32() changed the value of the pointer instead of value >> > where it is pointing at. >> > * Launch enclave incorrectly used sigstruct attributes-field instead of >> > enclave attributes-field. >> > * Removed unused struct sgx_add_page_req from sgx_ioctl.c >> > * Removed unused sgx_has_sgx2. >> > * Updated arch/x86/include/asm/sgx.h so that it provides stub >> > implementations when sgx in not enabled. >> > * Removed cruft rdmsr-calls from sgx_set_pubkeyhash_msrs(). >> > * return -ENOMEM in sgx_alloc_page() when VA pages consume too much space >> > * removed unused global sgx_nr_pids >> > * moved sgx_encl_release to sgx_encl.c >> > * return -ERESTARTSYS instead of -EINTR in sgx_encl_init() >> > >> > Jarkko Sakkinen (11): >> > x86/sgx: Add ENCLS architectural error codes >> > x86/sgx: Add SGX1 and SGX2 architectural data structures >> > x86/sgx: Add wrappers for ENCLS leaf functions >> > x86/sgx: Add functions to allocate and free EPC pages >> > x86/sgx: Add the Linux SGX Enclave Driver >> > x86/sgx: Add provisioning >> > x86/sgx: Add swapping code to the core and SGX driver >> > x86/sgx: ptrace() support for the SGX driver >> > selftests/x86: Add a selftest for SGX >> > x86/sgx: Update MAINTAINERS >> > docs: x86/sgx: Document the enclave API >> > >> > Kai Huang (2): >> > x86/cpufeatures: Add Intel-defined SGX feature bit >> > x86/cpufeatures: Add Intel-defined SGX_LC feature bit >> > >> > Sean Christopherson (15): >> > x86/cpufeatures: Add SGX sub-features (as Linux-defined bits) >> > x86/msr: Add IA32_FEATURE_CONTROL.SGX_ENABLE definition >> > x86/msr: Add SGX Launch Control MSR definitions >> > x86/mm: x86/sgx: Add new 'PF_SGX' page fault error code bit >> > x86/mm: x86/sgx: Signal SIGSEGV for userspace #PFs w/ PF_SGX >> > x86/cpu/intel: Detect SGX support and update caps appropriately >> > x86/sgx: Enumerate and track EPC sections >> > x86/sgx: Add sgx_einit() for initializing enclaves >> > x86/vdso: Add support for exception fixup in vDSO functions >> > x86/fault: Add helper function to sanitize error code >> > x86/fault: Attempt to fixup unhandled #PF in vDSO before signaling >> > x86/traps: Attempt to fixup exceptions in vDSO before signaling >> > x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave >> > transitions >> > docs: x86/sgx: Add Architecture documentation >> > docs: x86/sgx: Document kernel internals >> > >> > Documentation/index.rst | 1 + >> > Documentation/ioctl/ioctl-number.txt | 1 + >> > Documentation/x86/index.rst | 10 + >> > Documentation/x86/sgx/1.Architecture.rst | 431 +++++++++ >> > Documentation/x86/sgx/2.Kernel-internals.rst | 56 ++ >> > Documentation/x86/sgx/3.API.rst | 27 + >> > Documentation/x86/sgx/index.rst | 18 + >> > MAINTAINERS | 12 + >> > arch/x86/Kconfig | 27 + >> > arch/x86/entry/vdso/Makefile | 6 +- >> > arch/x86/entry/vdso/extable.c | 37 + >> > arch/x86/entry/vdso/extable.h | 29 + >> > arch/x86/entry/vdso/vdso-layout.lds.S | 9 +- >> > arch/x86/entry/vdso/vdso.lds.S | 1 + >> > arch/x86/entry/vdso/vdso2c.h | 58 +- >> > arch/x86/entry/vdso/vsgx_enter_enclave.S | 101 +++ >> > arch/x86/include/asm/cpufeatures.h | 24 +- >> > arch/x86/include/asm/disabled-features.h | 14 +- >> > arch/x86/include/asm/msr-index.h | 8 + >> > arch/x86/include/asm/traps.h | 1 + >> > arch/x86/include/asm/vdso.h | 5 + >> > arch/x86/include/uapi/asm/sgx.h | 86 ++ >> > arch/x86/include/uapi/asm/sgx_errno.h | 91 ++ >> > arch/x86/kernel/cpu/Makefile | 1 + >> > arch/x86/kernel/cpu/intel.c | 39 + >> > arch/x86/kernel/cpu/scattered.c | 2 + >> > arch/x86/kernel/cpu/sgx/Makefile | 2 + >> > arch/x86/kernel/cpu/sgx/arch.h | 424 +++++++++ >> > arch/x86/kernel/cpu/sgx/driver/Makefile | 3 + >> > arch/x86/kernel/cpu/sgx/driver/driver.h | 38 + >> > arch/x86/kernel/cpu/sgx/driver/ioctl.c | 850 ++++++++++++++++++ >> > arch/x86/kernel/cpu/sgx/driver/main.c | 368 ++++++++ >> > arch/x86/kernel/cpu/sgx/encl.c | 709 +++++++++++++++ >> > arch/x86/kernel/cpu/sgx/encl.h | 136 +++ >> > arch/x86/kernel/cpu/sgx/encls.c | 22 + >> > arch/x86/kernel/cpu/sgx/encls.h | 244 +++++ >> > arch/x86/kernel/cpu/sgx/main.c | 360 ++++++++ >> > arch/x86/kernel/cpu/sgx/reclaim.c | 482 ++++++++++ >> > arch/x86/kernel/cpu/sgx/sgx.h | 90 ++ >> > arch/x86/kernel/traps.c | 14 + >> > arch/x86/mm/fault.c | 44 +- >> > tools/arch/x86/include/asm/cpufeatures.h | 21 +- >> > tools/testing/selftests/x86/Makefile | 10 + >> > tools/testing/selftests/x86/sgx/Makefile | 48 + >> > tools/testing/selftests/x86/sgx/defines.h | 39 + >> > tools/testing/selftests/x86/sgx/encl.c | 20 + >> > tools/testing/selftests/x86/sgx/encl.lds | 33 + >> > .../selftests/x86/sgx/encl_bootstrap.S | 94 ++ >> > tools/testing/selftests/x86/sgx/encl_piggy.S | 18 + >> > tools/testing/selftests/x86/sgx/encl_piggy.h | 14 + >> > tools/testing/selftests/x86/sgx/main.c | 279 ++++++ >> > tools/testing/selftests/x86/sgx/sgx_call.S | 15 + >> > tools/testing/selftests/x86/sgx/sgxsign.c | 508 +++++++++++ >> > .../testing/selftests/x86/sgx/signing_key.pem | 39 + >> > 54 files changed, 5987 insertions(+), 32 deletions(-) >> > create mode 100644 Documentation/x86/index.rst >> > create mode 100644 Documentation/x86/sgx/1.Architecture.rst >> > create mode 100644 Documentation/x86/sgx/2.Kernel-internals.rst >> > create mode 100644 Documentation/x86/sgx/3.API.rst >> > create mode 100644 Documentation/x86/sgx/index.rst >> > create mode 100644 arch/x86/entry/vdso/extable.c >> > create mode 100644 arch/x86/entry/vdso/extable.h >> > create mode 100644 arch/x86/entry/vdso/vsgx_enter_enclave.S >> > create mode 100644 arch/x86/include/uapi/asm/sgx.h >> > create mode 100644 arch/x86/include/uapi/asm/sgx_errno.h >> > create mode 100644 arch/x86/kernel/cpu/sgx/Makefile >> > create mode 100644 arch/x86/kernel/cpu/sgx/arch.h >> > create mode 100644 arch/x86/kernel/cpu/sgx/driver/Makefile >> > create mode 100644 arch/x86/kernel/cpu/sgx/driver/driver.h >> > create mode 100644 arch/x86/kernel/cpu/sgx/driver/ioctl.c >> > create mode 100644 arch/x86/kernel/cpu/sgx/driver/main.c >> > create mode 100644 arch/x86/kernel/cpu/sgx/encl.c >> > create mode 100644 arch/x86/kernel/cpu/sgx/encl.h >> > create mode 100644 arch/x86/kernel/cpu/sgx/encls.c >> > create mode 100644 arch/x86/kernel/cpu/sgx/encls.h >> > create mode 100644 arch/x86/kernel/cpu/sgx/main.c >> > create mode 100644 arch/x86/kernel/cpu/sgx/reclaim.c >> > create mode 100644 arch/x86/kernel/cpu/sgx/sgx.h >> > create mode 100644 tools/testing/selftests/x86/sgx/Makefile >> > create mode 100644 tools/testing/selftests/x86/sgx/defines.h >> > create mode 100644 tools/testing/selftests/x86/sgx/encl.c >> > create mode 100644 tools/testing/selftests/x86/sgx/encl.lds >> > create mode 100644 tools/testing/selftests/x86/sgx/encl_bootstrap.S >> > create mode 100644 tools/testing/selftests/x86/sgx/encl_piggy.S >> > create mode 100644 tools/testing/selftests/x86/sgx/encl_piggy.h >> > create mode 100644 tools/testing/selftests/x86/sgx/main.c >> > create mode 100644 tools/testing/selftests/x86/sgx/sgx_call.S >> > create mode 100644 tools/testing/selftests/x86/sgx/sgxsign.c >> > create mode 100644 tools/testing/selftests/x86/sgx/signing_key.pem >> > >> > -- >> > 2.19.1 >> > >> >> I'm on leave for this week and next week's Monday if you wonder why >> I'm >> so passive in the discussion. Looking at the things next week's Tue. >> >> Just a quick comment about Andy's proposal. Probably pretty DSO like >> ELF blob could work with an addition of a section called ".tcs" for >> entry points. They need to be recognized so that the loader can add >> them as TCS pages. > > Hmm. > > There's a decent argument for specifically supporting whatever format > Windows uses. There's also an argument for allowing one or more > enclaves to be bundled in a regular ELF DSO. FWIW, there's no > fundamental reason we can't support more than one type of enclave > format. It would also be better to contain sigstruct so that EINIT can be performed. I wonder if we could use dlopen() as call path for this? That would also provide all the DAC/MAC/whatever security checks. I can start to PoC this next week once I get back to work (back on next Tue). I haven't studied Windows format much but I'd *guess* it is a COFF DLL? For me easier path to get something done would to do ELF DSO first. As you said they both could be done, which means that Windows COFF could be upstreamed later on. If this approach works, it'd mean that no ioctl's would be required except SGX_ENCLAVE_SET_ATTRIBUTE. PS. This quote from LWN got a bit into my feelings: "After 20 revisions of the patch set over three years, the authors of this work (which was posted by Jarkko Sakkinen) might well be forgiven for thinking that it must be about ready for merging." I seriously do not make any pre-conclusions ever for any patch that I post when it should be merged, no matter how big or small :-) For me this is just work... /Jarkko
On Wed, Apr 24, 2019 at 03:17:47PM +0300, Jarkko Sakkinen wrote: > For me easier path to get something done would to do ELF DSO > first. As you said they both could be done, which means that > Windows COFF could be upstreamed later on. > > If this approach works, it'd mean that no ioctl's would be > required except SGX_ENCLAVE_SET_ATTRIBUTE. > > PS. This quote from LWN got a bit into my feelings: > > "After 20 revisions of the patch set over three years, the > authors of this work (which was posted by Jarkko Sakkinen) > might well be forgiven for thinking that it must be about > ready for merging." > > I seriously do not make any pre-conclusions ever for any patch > that I post when it should be merged, no matter how big or > small :-) For me this is just work... Just throwing this out of my head so that all options are considered but wouldn't one alternative to get things right be to replace ioctl with a syscall? Not endorsing this option in particular but I think you could get security right by doing this. Even with dlopen() you need ioctl's for setting attributes (e.g. provisioning) and EINIT. A syscall would be in some ways more sound. /Jarkko
Hi Andy, > So I think we need a better EADD ioctl that explicitly does work on > PROT_READ|PROT_EXEC enclave memory but makes up for by validating the > *source* of the data. The effect will be similar to mapping a > labeled, appraised, etc file as PROT_EXEC. Maybe, in extreme > pseudocode: > > fd = open(“/dev/sgx/enclave”); > ioctl(fd, SGX_CREATE_FROM_FILE, file_fd); // fd now inherits the LSM > label from the file, or is otherwise marked. > mmap(fd, PROT_READ|PROT_EXEC, ...); > > I suppose that an alternative would be to delegate all the EADD calls > to a privileged daemon, but that’s nasty. I agree with you that *source* of EPC pages shall be validated. But instead of doing it in the driver, I think an alternative could be to treat an enclave file as a regular shared object so all IMA/LSM checks could be triggered/performed as part of the loading process, then let the driver "copy" those pages to EPC. By "copy", I mean both the page content and _permissions_ are copied, which differs from the current driver's behavior of copying page content only (while permissions are taken from ioctl parameters). That said, if an adversary cannot inject any code into a process in regular pages, then it cannot inject any code to any EPC pages in that process either, simply because the latter depends on the former. Security wise, it is also assumed that duplicating (both content and permissions of) an existing page within a process will not threaten the security of that process. That assumption may not always be true but in practice, the current LSM architecture doesn't seem to have that in scope, so this proposal I think still aligns with LSM in that sense. If compared to the idea of "enclave loader inside kernel", I think this alternative is much simpler and more flexible. In particular, * It requires minimal change to the driver - just take EPCM permissions from source pages' VMA instead of from ioctl parameter. * It requires little change to user mode enclave loader - just mmap() enclave file in the same way as dlopen() would do, then all IMA/LSM checks applicable to shared objects will be triggered/performed transparently. * It doesn't assume/tie to specific enclave formats. * It is extensible - Today every regular page within a process is allowed implicitly to be the source for an EPC page. In future, if at all desirable/necessary, IMA/LSM could be extended to leave a flag somewhere (e.g. in VMA) to indicate explicitly whether a regular page (or range) could be a source for EPC. Then SGX specific hooks/policies could be supported easily. How do you think? -Cedric
On 2019-05-10 10:23, Xing, Cedric wrote: > ... I think an alternative could be to treat an enclave file as a regular shared object so all IMA/LSM checks could be triggered/performed as part of the loading process, then let the driver "copy" those pages to EPC. ... > > If compared to the idea of "enclave loader inside kernel", I think this alternative is much simpler and more flexible. In particular, > * It requires minimal change to the driver - just take EPCM permissions from source pages' VMA instead of from ioctl parameter. > * It requires little change to user mode enclave loader - just mmap() enclave file in the same way as dlopen() would do, then all IMA/LSM checks applicable to shared objects will be triggered/performed transparently. > * It doesn't assume/tie to specific enclave formats. It does assume a specific format, namely, that the memory layout (including page types/permissions) of the enclave can be represented in a "flat file" on disk, or at least that the enclave memory contents consist of 4096-byte chunks in that file. Of the formats I have described in my other email, the only format that satisfies this property is the "CPU-native (instruction stream)" format which is not in use today. The ELF/PE formats in use today don't satisfy this property as the files don't contain the TCS contents. The SGXS format doesn't satisfy this property because the enclave memory is represented with 256-byte chunks. > * It is extensible - Today every regular page within a process is allowed implicitly to be the source for an EPC page. In future, if at all desirable/necessary, IMA/LSM could be extended to leave a flag somewhere (e.g. in VMA) to indicate explicitly whether a regular page (or range) could be a source for EPC. Then SGX specific hooks/policies could be supported easily. > > How do you think? > > -Cedric > -- Jethro Beekman | Fortanix
On 5/10/19 10:37 AM, Jethro Beekman wrote: > It does assume a specific format, namely, that the memory layout > (including page types/permissions) of the enclave can be represented in > a "flat file" on disk, or at least that the enclave memory contents > consist of 4096-byte chunks in that file. I _think_ Cedric's point is that, to the kernel, /lib/x86_64-linux-gnu/libc.so.6 is a "flat file" because the kernel doesn't have any part in parsing the executable format of a shared library. I actually don't know how it works, though. Do we just just trust that the userspace parsing of the .so format is correct? Do we just assume that any part of a file passing IMA checks can be PROT_EXEC?
On 2019-05-10 10:54, Dave Hansen wrote: > On 5/10/19 10:37 AM, Jethro Beekman wrote: >> It does assume a specific format, namely, that the memory layout >> (including page types/permissions) of the enclave can be represented in >> a "flat file" on disk, or at least that the enclave memory contents >> consist of 4096-byte chunks in that file. > > I _think_ Cedric's point is that, to the kernel, > /lib/x86_64-linux-gnu/libc.so.6 is a "flat file" because the kernel > doesn't have any part in parsing the executable format of a shared library. > > I actually don't know how it works, though. Do we just just trust that > the userspace parsing of the .so format is correct? Do we just assume > that any part of a file passing IMA checks can be PROT_EXEC? > ELF files are explicitly designed such that you can map them (with mmap) in 4096-byte chunks. However, sometimes there's overlap and you will sometimes see that a particular offset is mapped twice because the first half of the page in the file belongs to an RX range and the second half to an R-only range. Also, ELF files don't (normally) describe stack, heap, etc. which you do need for enclaves. -- Jethro Beekman | Fortanix
Hi Dave, > On 5/10/19 10:37 AM, Jethro Beekman wrote: > > It does assume a specific format, namely, that the memory layout > > (including page types/permissions) of the enclave can be represented > in > > a "flat file" on disk, or at least that the enclave memory contents > > consist of 4096-byte chunks in that file. > > I _think_ Cedric's point is that, to the kernel, > /lib/x86_64-linux-gnu/libc.so.6 is a "flat file" because the kernel > doesn't have any part in parsing the executable format of a shared > library. > > I actually don't know how it works, though. Do we just just trust that > the userspace parsing of the .so format is correct? Do we just assume > that any part of a file passing IMA checks can be PROT_EXEC? The key idea here is for enclave files to "inherit" the checks applicable to regular shared objects. And how we are going to do it is for user process to map every loadable segment of the enclave file into its address space using *multiple* mmap() calls, just in the same way as dlopen() would do for loading regular shared objects. Then those open()/mmap() syscalls will trigger all applicable checks (by means of security_file_open(), security_mmap_file() and ima_file_mmap() hooks) transparently. That said, IMA/LSM implementations/policies will dictate the success/failure of those open()/mmap() syscalls. And by depending EPCM attributes on permissions of source VMAs, no EXEC page could be ever created unless the source page (which has to be EXEC, btw) has passed IMA/LSM checks (as if it were loaded from a regular shared object file). -Cedric
Hi Jethro, > ELF files are explicitly designed such that you can map them (with mmap) > in 4096-byte chunks. However, sometimes there's overlap and you will > sometimes see that a particular offset is mapped twice because the first > half of the page in the file belongs to an RX range and the second half > to an R-only range. Also, ELF files don't (normally) describe stack, > heap, etc. which you do need for enclaves. You have probably misread my email. By mmap(), I meant the enclave file would be mapped via *multiple* mmap() calls, in the same way as what dlopen() would do in loading regular shared object. The intention here is to make the enclave file subject to the same checks as regular shared objects. The other enclave components (e.g. TCS, heap, stack, etc.) will be created from writable pages within the calling process's address space as before. Similar to heaps/stacks in a regular process, those components don't have to be backed by disk files. The core idea is for SGX driver to "copy" not only the content but also permissions from a source page to the target EPC page. Given the existence of the source page, it can be concluded that the combination of content and permissions of that page has passed all IMA/LSM checks applicable to that page, hence "copying" that page to EPC with the same (or less) permission is *not* a circumvention of IMA/LSM policies. -Cedric
On 2019-05-10 11:56, Xing, Cedric wrote: > Hi Jethro, > >> ELF files are explicitly designed such that you can map them (with mmap) >> in 4096-byte chunks. However, sometimes there's overlap and you will >> sometimes see that a particular offset is mapped twice because the first >> half of the page in the file belongs to an RX range and the second half >> to an R-only range. Also, ELF files don't (normally) describe stack, >> heap, etc. which you do need for enclaves. > > You have probably misread my email. By mmap(), I meant the enclave file would be mapped via *multiple* mmap() calls, in the same way as what dlopen() would do in loading regular shared object. The intention here is to make the enclave file subject to the same checks as regular shared objects. No, I didn't misread your email. My original point still stands: requiring that an enclave's memory is created from one or more mmap calls of a file puts significant restrictions on the enclave's on-disk representation. -- Jethro Beekman | Fortanix
On Fri, May 10, 2019 at 12:04 PM Jethro Beekman <jethro@fortanix.com> wrote: > > On 2019-05-10 11:56, Xing, Cedric wrote: > > Hi Jethro, > > > >> ELF files are explicitly designed such that you can map them (with mmap) > >> in 4096-byte chunks. However, sometimes there's overlap and you will > >> sometimes see that a particular offset is mapped twice because the first > >> half of the page in the file belongs to an RX range and the second half > >> to an R-only range. Also, ELF files don't (normally) describe stack, > >> heap, etc. which you do need for enclaves. > > > > You have probably misread my email. By mmap(), I meant the enclave file would be mapped via *multiple* mmap() calls, in the same way as what dlopen() would do in loading regular shared object. The intention here is to make the enclave file subject to the same checks as regular shared objects. > > No, I didn't misread your email. My original point still stands: > requiring that an enclave's memory is created from one or more mmap > calls of a file puts significant restrictions on the enclave's on-disk > representation. > For a tiny bit of background, Linux (AFAIK*) makes no effort to ensure the complete integrity of DSOs. What Linux *does* do (if so configured) is to make sure that only approved data is mapped executable. So, if you want to have some bytes be executable, those bytes have to come from a file that passes the relevant LSM and IMA checks. So we have two valid approaches, I think. Approach 1: we treat SGX exactly the same way and make it so that only bytes that pass the relevant checks can be mapped as code within an enclave. This imposes no particular restrictions on the file format -- we just need some API that takes an fd, an offset, and a length, and adds those bytes as code to an enclave. (It could also take a pointer and a length and make sure that the pointer points to executable memory -- same effect.) Approach 2: we decide that we want a stronger guarantee and that we *will* ensure the integrity of the enclave. This means: 2a) that we either need to load the entire thing from some approved file, and we commit to supporting one or more file formats. 2b) we need to check that the eventual enclave hash is approved. Or we could have a much shorter file that is just the hash and we check that. At its simplest, the file could be *only* the hash, and there could be an LSM callback to check it. In the future, if someone wants to allow enclaves to be embedded in DSOs, we could have a special ELF note or similar that contains an enclave hash or similar. 2c) same as 2b except that we expose the whole SIGSTRUCT, not just the hash. Here are some pros and cons of various bits: 1 and 2a allow anti-virus software to scan the enclave code, and 2a allows it to scan the whole enclave. I don't know if this is actually imporant. 2a is by far the most complicated kernel implementation. 2b and 2c are almost file-format agnostic. 1 is completely file format agnostic but, in exchange, it's much weaker. 2b and 2c should solve most (but not all) of the launch control complaints that Dr. Greg cares about, in the sense that the LSM policy quite literally validates that the enclave is approved. As a straw man design, I propose the following, which is mostly 2c. The whole loading process works almost as in Jarkko's current driver, but the actual ioctl that triggers EINIT changes. When you issue the ioctl, you pass in an fd and the SIGSTRUCT is loaded and checked from the fd. The idea is that software that ships an enclave will ship a .sgxsig file that is literally a SIGSTRUCT for the enclave. With SELinux, that file gets labeled something like sgx_enclave_sigstruct_t. And we have the following extra twist: if you're calling the EADD ioctl to add *code* to the enclave, the driver checks that the code being loaded is mapped executable. This way existing code-signing policies don't get subverted, and policies that want to impose full verification on the enclave can do so by verifying the .sigstruct file. What do you all think? * It's certainly the case that Linux does not *succeed* at preserving the overall integrity of shared objects. If nothing else, you can freely mremap() them however you like. And you can jump into them wherever you like.
Hi Andy and Jethro, > > > You have probably misread my email. By mmap(), I meant the enclave > file would be mapped via *multiple* mmap() calls, in the same way as > what dlopen() would do in loading regular shared object. The intention > here is to make the enclave file subject to the same checks as regular > shared objects. > > > > No, I didn't misread your email. My original point still stands: > > requiring that an enclave's memory is created from one or more mmap > > calls of a file puts significant restrictions on the enclave's on-disk > > representation. > > I was talking in the context of ELF, with the assumption that changing RW pages to RX is disallowed by LSM. And in that case mmap()would be the only way to load a page from disk without having to "write" to it. But that's just an example but not the focus of the discussion. The point I was trying to make was, that the driver is going to copy both content and permissions from the source so the security properties established (by IMA/LSM) around that source page would be carried onto the EPC page being EADD'ed. The driver doesn't care how that source page came into existence. It could be mapped from an ELF file as in the example, or it could be a result from JIT as long as LSM allows it. The driver will be file format agnostic. > > For a tiny bit of background, Linux (AFAIK*) makes no effort to ensure > the complete integrity of DSOs. What Linux *does* do (if so > configured) is to make sure that only approved data is mapped > executable. So, if you want to have some bytes be executable, those > bytes have to come from a file that passes the relevant LSM and IMA > checks. So we have two valid approaches, I think. > > Approach 1: we treat SGX exactly the same way and make it so that only > bytes that pass the relevant checks can be mapped as code within an > enclave. This imposes no particular restrictions on the file format > -- we just need some API that takes an fd, an offset, and a length, > and adds those bytes as code to an enclave. (It could also take a > pointer and a length and make sure that the pointer points to > executable memory -- same effect.) I assume "some API" is some user mode API so this approach is the same as what I suggested in my last email. Am I correct? > > Approach 2: we decide that we want a stronger guarantee and that we > *will* ensure the integrity of the enclave. This means: > > 2a) that we either need to load the entire thing from some approved > file, and we commit to supporting one or more file formats. > > 2b) we need to check that the eventual enclave hash is approved. Or > we could have a much shorter file that is just the hash and we check > that. At its simplest, the file could be *only* the hash, and there > could be an LSM callback to check it. In the future, if someone wants > to allow enclaves to be embedded in DSOs, we could have a special ELF > note or similar that contains an enclave hash or similar. > > 2c) same as 2b except that we expose the whole SIGSTRUCT, not just the > hash. > > Here are some pros and cons of various bits: > > 1 and 2a allow anti-virus software to scan the enclave code, and 2a > allows it to scan the whole enclave. I don't know if this is actually > imporant. I guess anti-virus software can scan any enclave file in *all* cases as long as it understands the format of that enclave. It doesn't necessary mean the kernel has to understand that enclave format (as enclave file could be parsed in user mode) or the anti-virus software has to understand all formats (if any) supported natively by the kernel. > > 2a is by far the most complicated kernel implementation. > Agreed. I don't see any reason 2a would be necessary. > 2b and 2c are almost file-format agnostic. 1 is completely file > format agnostic but, in exchange, it's much weaker. I'd say 1 and (variants of) 2 are orthogonal. SGX always enforces integrities so not doing integrity checks at EADD doesn't mean the enclave integrity is not being enforced. 1 and 2 are basically 2 different checkpoints where LSM hooks could be placed. And a given LSM implementation/policy may enforce either 1 or 2, or both, or neither. > > 2b and 2c should solve most (but not all) of the launch control > complaints that Dr. Greg cares about, in the sense that the LSM policy > quite literally validates that the enclave is approved. > > As a straw man design, I propose the following, which is mostly 2c. > The whole loading process works almost as in Jarkko's current driver, > but the actual ioctl that triggers EINIT changes. When you issue the > ioctl, you pass in an fd and the SIGSTRUCT is loaded and checked from > the fd. The idea is that software that ships an enclave will ship a > .sgxsig file that is literally a SIGSTRUCT for the enclave. With > SELinux, that file gets labeled something like > sgx_enclave_sigstruct_t. And we have the following extra twist: if > you're calling the EADD ioctl to add *code* to the enclave, the driver > checks that the code being loaded is mapped executable. This way > existing code-signing policies don't get subverted, and policies that > want to impose full verification on the enclave can do so by verifying > the .sigstruct file. I'm with you that it's desirable/necessary to add an LSM hook at EINIT, but don't see the need for .sigstruct file or its fd as input to EINIT ioctl. Generally speaking, LSM needs to decide whether or not to launch the enclave in question. And that decision could be based upon either the enclave itself (i.e. bytes comprising the enclave, or its MRENCLAVE, or its signature, all equivalent from cryptographic standpoint), or some external attributes associated with the enclave (e.g. DAC/MAC context associated with the enclave file), or both. In the former case, what matters is the content of the SIGSTRUCT but not where it came from; while in the latter case it could be gated at open() syscall so that no fd to SIGSTRUCT (or the enclave image file) could ever be obtained by the calling process if it was not allowed to launch that enclave at all. In either case, no fd is necessary to be passed to EINIT ioctl. That said, by providing a SIGSTRUCT to EINIT ioctl, the calling process has implicitly proven its access to needed file(s) at the file system level, so only the content (i.e. MRENCLAVE or signing key) of the SIGSTRUCT needs to be checked by LSM, while the integrity of the enclave will be enforced by SGX hardware. > > What do you all think? > I think approach 1 and (variants of) 2 are orthogonal so I wouldn't skip either to make the other mandatory from architecture perspective; while an LSM policy may opt to enforce either one, or both. Putting everything together, I'd suggest to: - Change EADD ioctl to take source page's VMA permission as ("upper bound" of) EPCM permission. This make sure no one can circumvent LSM to generate executable code on the fly using SGX driver. - Change EINIT ioctl to invoke (new?) LSM hook to validate SIGSTRUCT before issuing EINIT. > * It's certainly the case that Linux does not *succeed* at preserving > the overall integrity of shared objects. If nothing else, you can > freely mremap() them however you like. And you can jump into them > wherever you like. -Cedric
On Mon, May 13, 2019 at 01:29:26PM +0300, Jarkko Sakkinen wrote: > I did study through SDK's file format and realized that it does not > does make sense after all to embed one. > > To implement it properly you would probably need a new syscall (lets say > sgx_load_enclave) and also that enclaves are not just executables > binaries. It is hard to find a generic format for them as applications > range from simply protecting part of an application to running a > containter inside enclave. When I looked at SDK's data structures embedded to the ELF they contain data that is also used by the run-time. Thus, run-time would anyway need to also parse the file containing the enclave. There is not same way a single rigid structure of what executable means as for normal application and in many cases enclave could be just used to seal a portion of the code. In extreme case you might construct enclave even on run-time. I think SIGSTRUCT in a file is the right choice. It is the lowest common denominator for enclaves that locks in its contents and is same for any enclave. Practicing access control to that file should be enough to define whatever security policy required. I'm still puzzling what kind of changes you were discussing considering SGX_IOC_ENCLAVE_ADD_PAGE. /Jarkko
On Fri, 10 May 2019 14:22:34 -0500, Andy Lutomirski <luto@kernel.org> wrote: > On Fri, May 10, 2019 at 12:04 PM Jethro Beekman <jethro@fortanix.com> > wrote: >> >> On 2019-05-10 11:56, Xing, Cedric wrote: >> > Hi Jethro, >> > >> >> ELF files are explicitly designed such that you can map them (with >> mmap) >> >> in 4096-byte chunks. However, sometimes there's overlap and you will >> >> sometimes see that a particular offset is mapped twice because the >> first >> >> half of the page in the file belongs to an RX range and the second >> half >> >> to an R-only range. Also, ELF files don't (normally) describe stack, >> >> heap, etc. which you do need for enclaves. >> > >> > You have probably misread my email. By mmap(), I meant the enclave >> file would be mapped via *multiple* mmap() calls, in the same way as >> what dlopen() would do in loading regular shared object. The intention >> here is to make the enclave file subject to the same checks as regular >> shared objects. >> >> No, I didn't misread your email. My original point still stands: >> requiring that an enclave's memory is created from one or more mmap >> calls of a file puts significant restrictions on the enclave's on-disk >> representation. >> > > For a tiny bit of background, Linux (AFAIK*) makes no effort to ensure > the complete integrity of DSOs. What Linux *does* do (if so > configured) is to make sure that only approved data is mapped > executable. So, if you want to have some bytes be executable, those > bytes have to come from a file that passes the relevant LSM and IMA > checks. Given this, I just want to step back a little to understand the exact issue that SGX is causing here for LSM/IMA. Sorry if I missed points discussed earlier. By the time of EADD, enclave file is opened and should have passed IMA and SELinux policy enforcement gates if any. We really don't need extra mmaps on the enclave files to be IMA and SELinux compliant. We are loading enclave files as RO and copying those into EPC. An IMA policy can enforce RO files (or any file). And SELinux policy can say which processes can open the file for what permissions. No extra needed here. And sgx enclaves are always signed and integrity protected and verified at the time of EINIT. So if EINIT passes, we know the content loaded (including permission flags) is matching the sigstruct. But sigstruct/signature is part of the file, should be accounted for in IMA measurement of the whole file, so it is also verified by IMA during file open, right? The only potential gap/difference comparing to regular ELF executable or DSOs:for enclaves, we need mmap portions of enclave linear range with RW to do EADD IOC, then mprotect those pages to RX after EINIT. But this is operated on enclave fd provided by driver. So we can have an SELinux policy say: only this type of processes is allowed to open enclave fd, and allowed to do mmap/mprotect with read, write, execute on it. Wouldn't that be enough? Thanks Haitao
On Fri, May 10, 2019 at 6:06 PM Xing, Cedric <cedric.xing@intel.com> wrote: > > Hi Andy and Jethro, > > > > > You have probably misread my email. By mmap(), I meant the enclave > > file would be mapped via *multiple* mmap() calls, in the same way as > > what dlopen() would do in loading regular shared object. The intention > > here is to make the enclave file subject to the same checks as regular > > shared objects. > > > > > > No, I didn't misread your email. My original point still stands: > > > requiring that an enclave's memory is created from one or more mmap > > > calls of a file puts significant restrictions on the enclave's on-disk > > > representation. > > > > > I was talking in the context of ELF, with the assumption that changing RW pages to RX is disallowed by LSM. And in that case mmap()would be the only way to load a page from disk without having to "write" to it. But that's just an example but not the focus of the discussion. > > The point I was trying to make was, that the driver is going to copy both content and permissions from the source so the security properties established (by IMA/LSM) around that source page would be carried onto the EPC page being EADD'ed. The driver doesn't care how that source page came into existence. It could be mapped from an ELF file as in the example, or it could be a result from JIT as long as LSM allows it. The driver will be file format agnostic. > > > > > For a tiny bit of background, Linux (AFAIK*) makes no effort to ensure > > the complete integrity of DSOs. What Linux *does* do (if so > > configured) is to make sure that only approved data is mapped > > executable. So, if you want to have some bytes be executable, those > > bytes have to come from a file that passes the relevant LSM and IMA > > checks. So we have two valid approaches, I think. > > > > Approach 1: we treat SGX exactly the same way and make it so that only > > bytes that pass the relevant checks can be mapped as code within an > > enclave. This imposes no particular restrictions on the file format > > -- we just need some API that takes an fd, an offset, and a length, > > and adds those bytes as code to an enclave. (It could also take a > > pointer and a length and make sure that the pointer points to > > executable memory -- same effect.) > > I assume "some API" is some user mode API so this approach is the same as what I suggested in my last email. Am I correct? I meant kernel, but SGX_IOC_ADD_ENCLAVE_PAGE could be that API. The only benefit I can see to making the kernel API take an fd instead of a pointer is that it allows loading an enclave without mapping it first, but that seems unlikely to be very important. > > > > > Approach 2: we decide that we want a stronger guarantee and that we > > *will* ensure the integrity of the enclave. This means: > > > > 2a) that we either need to load the entire thing from some approved > > file, and we commit to supporting one or more file formats. > > > > 2b) we need to check that the eventual enclave hash is approved. Or > > we could have a much shorter file that is just the hash and we check > > that. At its simplest, the file could be *only* the hash, and there > > could be an LSM callback to check it. In the future, if someone wants > > to allow enclaves to be embedded in DSOs, we could have a special ELF > > note or similar that contains an enclave hash or similar. > > > > 2c) same as 2b except that we expose the whole SIGSTRUCT, not just the > > hash. > > > > Here are some pros and cons of various bits: > > > > 1 and 2a allow anti-virus software to scan the enclave code, and 2a > > allows it to scan the whole enclave. I don't know if this is actually > > imporant. > > I guess anti-virus software can scan any enclave file in *all* cases as long as it understands the format of that enclave. It doesn't necessary mean the kernel has to understand that enclave format (as enclave file could be parsed in user mode) or the anti-virus software has to understand all formats (if any) supported natively by the kernel. True, but in 2a it can be scanned even without understanding the format. That's probably not terribly important. > > > > > 2a is by far the most complicated kernel implementation. > > > > Agreed. I don't see any reason 2a would be necessary. > > > 2b and 2c are almost file-format agnostic. 1 is completely file > > format agnostic but, in exchange, it's much weaker. > > I'd say 1 and (variants of) 2 are orthogonal. SGX always enforces integrities so not doing integrity checks at EADD doesn't mean the enclave integrity is not being enforced. 1 and 2 are basically 2 different checkpoints where LSM hooks could be placed. And a given LSM implementation/policy may enforce either 1 or 2, or both, or neither. They're not orthogonal in the sense that verifying the SIGSTRUCT implicitly verifies the executable contents. > > > > > 2b and 2c should solve most (but not all) of the launch control > > complaints that Dr. Greg cares about, in the sense that the LSM policy > > quite literally validates that the enclave is approved. > > > > As a straw man design, I propose the following, which is mostly 2c. > > The whole loading process works almost as in Jarkko's current driver, > > but the actual ioctl that triggers EINIT changes. When you issue the > > ioctl, you pass in an fd and the SIGSTRUCT is loaded and checked from > > the fd. The idea is that software that ships an enclave will ship a > > .sgxsig file that is literally a SIGSTRUCT for the enclave. With > > SELinux, that file gets labeled something like > > sgx_enclave_sigstruct_t. And we have the following extra twist: if > > you're calling the EADD ioctl to add *code* to the enclave, the driver > > checks that the code being loaded is mapped executable. This way > > existing code-signing policies don't get subverted, and policies that > > want to impose full verification on the enclave can do so by verifying > > the .sigstruct file. > > I'm with you that it's desirable/necessary to add an LSM hook at EINIT, but don't see the need for .sigstruct file or its fd as input to EINIT ioctl. > > Generally speaking, LSM needs to decide whether or not to launch the enclave in question. And that decision could be based upon either the enclave itself (i.e. bytes comprising the enclave, or its MRENCLAVE, or its signature, all equivalent from cryptographic standpoint), or some external attributes associated with the enclave (e.g. DAC/MAC context associated with the enclave file), or both. In the former case, what matters is the content of the SIGSTRUCT but not where it came from; while in the latter case it could be gated at open() syscall so that no fd to SIGSTRUCT (or the enclave image file) could ever be obtained by the calling process if it was not allowed to launch that enclave at all. In either case, no fd is necessary to be passed to EINIT ioctl. That said, by providing a SIGSTRUCT to EINIT ioctl, the calling process has implicitly proven its access to needed file(s) at the file system level, so only the content (i.e. MRENCLAVE or signing key) of the SIGSTRUCT needs to be checked by LSM, while the integrity of the enclave will be enforced by SGX hardware. > I agree with you except that there's a difference in how the configuration works. If the whole .sigstruct is in a file, then teaching your policy to accept an enclave works just like teaching your policy to accept a new ELF program -- you just put it in a file and mark the file trusted (by labeling it, for example). If the .sigstruct comes from unverifiable application memory, then an out-of-band mechanism will likely be needed. > Putting everything together, I'd suggest to: > - Change EADD ioctl to take source page's VMA permission as ("upper bound" of) EPCM permission. This make sure no one can circumvent LSM to generate executable code on the fly using SGX driver. > - Change EINIT ioctl to invoke (new?) LSM hook to validate SIGSTRUCT before issuing EINIT. I'm okay with this if the consensus is that having a .sigstruct file is too annoying. (Note that a .sigstruct file does not, strictly speaking, require that it comes from a real file. It could come from memfd, and it will then fail to load if the LSM doesn't like it. So you can still easily download and run an enclave if you think that's a good idea.)
On Tue, May 14, 2019 at 3:43 AM Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > > On Mon, May 13, 2019 at 01:29:26PM +0300, Jarkko Sakkinen wrote: > > I did study through SDK's file format and realized that it does not > > does make sense after all to embed one. > > > > To implement it properly you would probably need a new syscall (lets say > > sgx_load_enclave) and also that enclaves are not just executables > > binaries. It is hard to find a generic format for them as applications > > range from simply protecting part of an application to running a > > containter inside enclave. > > I'm still puzzling what kind of changes you were discussing considering > SGX_IOC_ENCLAVE_ADD_PAGE. I think it's as simple as requiring that, if SECINFO.X is set, then the src pointer points to the appropriate number of bytes of executable memory. (Unless there's some way for an enclave to change SECINFO after the fact -- is there?) Sadly, we don't really have the a nice in-kernel API for that right now. You could do down_read(mmap_sem) and find_vma(). Arguably there is no value to checking that PKRU allows execute to the data. Hey, Dave, if you're still paying attention to this thread, should we have copy_from_user_exec() that does the right thing wrt the page permissions and PKRU.
On Tue, May 14, 2019 at 7:33 AM Haitao Huang <haitao.huang@linux.intel.com> wrote: > > On Fri, 10 May 2019 14:22:34 -0500, Andy Lutomirski <luto@kernel.org> > wrote: > > > On Fri, May 10, 2019 at 12:04 PM Jethro Beekman <jethro@fortanix.com> > > wrote: > >> > >> On 2019-05-10 11:56, Xing, Cedric wrote: > >> > Hi Jethro, > >> > > >> >> ELF files are explicitly designed such that you can map them (with > >> mmap) > >> >> in 4096-byte chunks. However, sometimes there's overlap and you will > >> >> sometimes see that a particular offset is mapped twice because the > >> first > >> >> half of the page in the file belongs to an RX range and the second > >> half > >> >> to an R-only range. Also, ELF files don't (normally) describe stack, > >> >> heap, etc. which you do need for enclaves. > >> > > >> > You have probably misread my email. By mmap(), I meant the enclave > >> file would be mapped via *multiple* mmap() calls, in the same way as > >> what dlopen() would do in loading regular shared object. The intention > >> here is to make the enclave file subject to the same checks as regular > >> shared objects. > >> > >> No, I didn't misread your email. My original point still stands: > >> requiring that an enclave's memory is created from one or more mmap > >> calls of a file puts significant restrictions on the enclave's on-disk > >> representation. > >> > > > > For a tiny bit of background, Linux (AFAIK*) makes no effort to ensure > > the complete integrity of DSOs. What Linux *does* do (if so > > configured) is to make sure that only approved data is mapped > > executable. So, if you want to have some bytes be executable, those > > bytes have to come from a file that passes the relevant LSM and IMA > > checks. > > Given this, I just want to step back a little to understand the exact > issue that SGX is causing here for LSM/IMA. Sorry if I missed points > discussed earlier. > > By the time of EADD, enclave file is opened and should have passed IMA and > SELinux policy enforcement gates if any. We really don't need extra mmaps > on the enclave files to be IMA and SELinux compliant. The problem, as i see it, is that they passed the *wrong* checks, because, as you noticed: > We are loading > enclave files as RO and copying those into EPC. Which is, semantically, a lot like loading a normal file as RO and then mprotecting() it to RX, which is disallowed under quite a few LSM policies. > An IMA policy can enforce > RO files (or any file). And SELinux policy can say which processes can > open the file for what permissions. No extra needed here. If SELinux says a process may open a file as RO, that does *not* mean that it can be opened as RX. > > And sgx enclaves are always signed and integrity protected and verified at > the time of EINIT. So if EINIT passes, we know the content loaded > (including permission flags) is matching the sigstruct. But > sigstruct/signature is part of the file, should be accounted for in IMA > measurement of the whole file, so it is also verified by IMA during file > open, right? This does work, but only if the kernel parses that file so that the kernel can trust that the enclave data actually came from the file as intended. And moving the parsing to the kernel seems like a mess that no one really wants to do.
On Tue, 14 May 2019 10:17:29 -0500, Andy Lutomirski <luto@kernel.org> wrote: > On Tue, May 14, 2019 at 7:33 AM Haitao Huang > <haitao.huang@linux.intel.com> wrote: >> >> On Fri, 10 May 2019 14:22:34 -0500, Andy Lutomirski <luto@kernel.org> >> wrote: >> >> > On Fri, May 10, 2019 at 12:04 PM Jethro Beekman <jethro@fortanix.com> >> > wrote: >> >> >> >> On 2019-05-10 11:56, Xing, Cedric wrote: >> >> > Hi Jethro, >> >> > >> >> >> ELF files are explicitly designed such that you can map them (with >> >> mmap) >> >> >> in 4096-byte chunks. However, sometimes there's overlap and you >> will >> >> >> sometimes see that a particular offset is mapped twice because the >> >> first >> >> >> half of the page in the file belongs to an RX range and the second >> >> half >> >> >> to an R-only range. Also, ELF files don't (normally) describe >> stack, >> >> >> heap, etc. which you do need for enclaves. >> >> > >> >> > You have probably misread my email. By mmap(), I meant the enclave >> >> file would be mapped via *multiple* mmap() calls, in the same way as >> >> what dlopen() would do in loading regular shared object. The >> intention >> >> here is to make the enclave file subject to the same checks as >> regular >> >> shared objects. >> >> >> >> No, I didn't misread your email. My original point still stands: >> >> requiring that an enclave's memory is created from one or more mmap >> >> calls of a file puts significant restrictions on the enclave's >> on-disk >> >> representation. >> >> >> > >> > For a tiny bit of background, Linux (AFAIK*) makes no effort to ensure >> > the complete integrity of DSOs. What Linux *does* do (if so >> > configured) is to make sure that only approved data is mapped >> > executable. So, if you want to have some bytes be executable, those >> > bytes have to come from a file that passes the relevant LSM and IMA >> > checks. >> >> Given this, I just want to step back a little to understand the exact >> issue that SGX is causing here for LSM/IMA. Sorry if I missed points >> discussed earlier. >> >> By the time of EADD, enclave file is opened and should have passed IMA >> and >> SELinux policy enforcement gates if any. We really don't need extra >> mmaps >> on the enclave files to be IMA and SELinux compliant. > > The problem, as i see it, is that they passed the *wrong* checks, > because, as you noticed: > >> We are loading >> enclave files as RO and copying those into EPC. > > Which is, semantically, a lot like loading a normal file as RO and > then mprotecting() it to RX, which is disallowed under quite a few LSM > policies. > >> An IMA policy can enforce >> RO files (or any file). And SELinux policy can say which processes can >> open the file for what permissions. No extra needed here. > > If SELinux says a process may open a file as RO, that does *not* mean > that it can be opened as RX. > But in this case, file itself is mapped as RO treated like data and it is not for execution. SGX enclave pages have EPCM enforced permissions. So from SELinux point of view I would think it can treat it as RO and that's fine. >> >> And sgx enclaves are always signed and integrity protected and verified >> at >> the time of EINIT. So if EINIT passes, we know the content loaded >> (including permission flags) is matching the sigstruct. But >> sigstruct/signature is part of the file, should be accounted for in IMA >> measurement of the whole file, so it is also verified by IMA during file >> open, right? > > This does work, but only if the kernel parses that file so that the > kernel can trust that the enclave data actually came from the file as > intended. And moving the parsing to the kernel seems like a mess that > no one really wants to do. If kernel only needs to know the source bytes are from a file that passed IMA integrity and SELinux RO enforcement, then it can just check if the source pointer belongs to a VMA with valid fd and no parsing or checking permissions needed. Understood if you want to make enclave file code segment stick to the RX semantics mentioned above, then this doesn't qualify. Thanks Haitao
On Tue, May 14, 2019 at 08:13:36AM -0700, Andy Lutomirski wrote: > On Tue, May 14, 2019 at 3:43 AM Jarkko Sakkinen > <jarkko.sakkinen@linux.intel.com> wrote: > > > > On Mon, May 13, 2019 at 01:29:26PM +0300, Jarkko Sakkinen wrote: > > > I did study through SDK's file format and realized that it does not > > > does make sense after all to embed one. > > > > > > To implement it properly you would probably need a new syscall (lets say > > > sgx_load_enclave) and also that enclaves are not just executables > > > binaries. It is hard to find a generic format for them as applications > > > range from simply protecting part of an application to running a > > > containter inside enclave. > > > > I'm still puzzling what kind of changes you were discussing considering > > SGX_IOC_ENCLAVE_ADD_PAGE. > > I think it's as simple as requiring that, if SECINFO.X is set, then > the src pointer points to the appropriate number of bytes of > executable memory. (Unless there's some way for an enclave to change > SECINFO after the fact -- is there?) Nit: SECINFO is just the struct passed to EADD, I think what you're really asking is "can the EPCM permissions be changed after the fact". And the answer is, yes. On SGX2 hardware, the enclave can extend the EPCM permissions at runtime via ENCLU[EMODPE], e.g. to make a page writable. Hardware also doesn't prevent doing EADD to the same virtual address multiple times, e.g. an enclave could EADD a RX page, and then EADD a RW page at the same virtual address with different data. The second EADD will affect MRENCLAVE, but so long as it's accounted for by the enclave's signer, it's "legal". SGX_IOC_ENCLAVE_ADD_PAGE *does* prevent adding the "same" page to an enclave multiple times, so effectively this scenario is blocked by the current implementation, but it's more of a side effect (of a sane implementation) as opposed to deliberately preventing shenanigans. Regarding EMODPE, the kernel doesn't rely on EPCM permissions in any way shape or form (the EPCM permissions are purely to protect the enclave from the kernel), e.g. adding +X to a page in the EPCM doesn't magically change the kernel's page tables and attempting to execute from the page will still generate a (non-SGX) #PF. So rather than check SECINFO.X, I think we'd want to have EADD check that the permissions in SECINFO are a subset of the VMA's perms (IIUC, this is essentially what Cedric proposed). That would prevent using EMODPE to gain executable permissions, and would explicitly deny the scenario of a double EADD to load non-executable data into an executable page. Oh, and EADD should probably also require EEXTEND for executable pages. > Sadly, we don't really have the a nice in-kernel API for that right now. > You could do down_read(mmap_sem) and find_vma(). Arguably there is no > value to checking that PKRU allows execute to the data. > > Hey, Dave, if you're still paying attention to this thread, should we > have copy_from_user_exec() that does the right thing wrt the page > permissions and PKRU.
> On May 14, 2019, at 8:30 AM, Haitao Huang <haitao.huang@linux.intel.com> wrote: > >> On Tue, 14 May 2019 10:17:29 -0500, Andy Lutomirski <luto@kernel.org> wrote: >> >> On Tue, May 14, 2019 at 7:33 AM Haitao Huang >> <haitao.huang@linux.intel.com> wrote: >>> >>> On Fri, 10 May 2019 14:22:34 -0500, Andy Lutomirski <luto@kernel.org> >>> wrote: >>> >>> > On Fri, May 10, 2019 at 12:04 PM Jethro Beekman <jethro@fortanix.com> >>> > wrote: >>> >> >>> >> On 2019-05-10 11:56, Xing, Cedric wrote: >>> >> > Hi Jethro, >>> >> > >>> >> >> ELF files are explicitly designed such that you can map them (with >>> >> mmap) >>> >> >> in 4096-byte chunks. However, sometimes there's overlap and you will >>> >> >> sometimes see that a particular offset is mapped twice because the >>> >> first >>> >> >> half of the page in the file belongs to an RX range and the second >>> >> half >>> >> >> to an R-only range. Also, ELF files don't (normally) describe stack, >>> >> >> heap, etc. which you do need for enclaves. >>> >> > >>> >> > You have probably misread my email. By mmap(), I meant the enclave >>> >> file would be mapped via *multiple* mmap() calls, in the same way as >>> >> what dlopen() would do in loading regular shared object. The intention >>> >> here is to make the enclave file subject to the same checks as regular >>> >> shared objects. >>> >> >>> >> No, I didn't misread your email. My original point still stands: >>> >> requiring that an enclave's memory is created from one or more mmap >>> >> calls of a file puts significant restrictions on the enclave's on-disk >>> >> representation. >>> >> >>> > >>> > For a tiny bit of background, Linux (AFAIK*) makes no effort to ensure >>> > the complete integrity of DSOs. What Linux *does* do (if so >>> > configured) is to make sure that only approved data is mapped >>> > executable. So, if you want to have some bytes be executable, those >>> > bytes have to come from a file that passes the relevant LSM and IMA >>> > checks. >>> >>> Given this, I just want to step back a little to understand the exact >>> issue that SGX is causing here for LSM/IMA. Sorry if I missed points >>> discussed earlier. >>> >>> By the time of EADD, enclave file is opened and should have passed IMA and >>> SELinux policy enforcement gates if any. We really don't need extra mmaps >>> on the enclave files to be IMA and SELinux compliant. >> >> The problem, as i see it, is that they passed the *wrong* checks, >> because, as you noticed: >> >>> We are loading >>> enclave files as RO and copying those into EPC. >> >> Which is, semantically, a lot like loading a normal file as RO and >> then mprotecting() it to RX, which is disallowed under quite a few LSM >> policies. >> >>> An IMA policy can enforce >>> RO files (or any file). And SELinux policy can say which processes can >>> open the file for what permissions. No extra needed here. >> >> If SELinux says a process may open a file as RO, that does *not* mean >> that it can be opened as RX. >> > > But in this case, file itself is mapped as RO treated like data and it is not for execution. SGX enclave pages have EPCM enforced permissions. So from SELinux point of view I would think it can treat it as RO and that's fine. As an example, SELinux has an “execute” permission (via security_mmap_file — see file_map_prot_check()) that controls whether you can execute code from that file. If you lack this permission on a file, you may still be able to map it PROT_READ, but you may not map it PROT_EXEC. Similarly, if you want to malloc() some memory, write *code* to it, and execute it, you need a specific permission. So, unless we somehow think it’s okay for SGX to break the existing model, we need to respect these restrictions in the SGX driver. In other words, we either need to respect execmem, etc or require PROT_EXEC or the equivalent. I like the latter a lot more.
On Tue, 14 May 2019 15:45:54 -0500, Andy Lutomirski <luto@kernel.org> wrote: >> On May 14, 2019, at 8:30 AM, Haitao Huang >> <haitao.huang@linux.intel.com> wrote: >> >>> On Tue, 14 May 2019 10:17:29 -0500, Andy Lutomirski <luto@kernel.org> >>> wrote: >>> >>> On Tue, May 14, 2019 at 7:33 AM Haitao Huang >>> <haitao.huang@linux.intel.com> wrote: >>>> >>>> On Fri, 10 May 2019 14:22:34 -0500, Andy Lutomirski <luto@kernel.org> >>>> wrote: >>>> >>>> > On Fri, May 10, 2019 at 12:04 PM Jethro Beekman >>>> <jethro@fortanix.com> >>>> > wrote: >>>> >> >>>> >> On 2019-05-10 11:56, Xing, Cedric wrote: >>>> >> > Hi Jethro, >>>> >> > >>>> >> >> ELF files are explicitly designed such that you can map them >>>> (with >>>> >> mmap) >>>> >> >> in 4096-byte chunks. However, sometimes there's overlap and you >>>> will >>>> >> >> sometimes see that a particular offset is mapped twice because >>>> the >>>> >> first >>>> >> >> half of the page in the file belongs to an RX range and the >>>> second >>>> >> half >>>> >> >> to an R-only range. Also, ELF files don't (normally) describe >>>> stack, >>>> >> >> heap, etc. which you do need for enclaves. >>>> >> > >>>> >> > You have probably misread my email. By mmap(), I meant the >>>> enclave >>>> >> file would be mapped via *multiple* mmap() calls, in the same way >>>> as >>>> >> what dlopen() would do in loading regular shared object. The >>>> intention >>>> >> here is to make the enclave file subject to the same checks as >>>> regular >>>> >> shared objects. >>>> >> >>>> >> No, I didn't misread your email. My original point still stands: >>>> >> requiring that an enclave's memory is created from one or more mmap >>>> >> calls of a file puts significant restrictions on the enclave's >>>> on-disk >>>> >> representation. >>>> >> >>>> > >>>> > For a tiny bit of background, Linux (AFAIK*) makes no effort to >>>> ensure >>>> > the complete integrity of DSOs. What Linux *does* do (if so >>>> > configured) is to make sure that only approved data is mapped >>>> > executable. So, if you want to have some bytes be executable, those >>>> > bytes have to come from a file that passes the relevant LSM and IMA >>>> > checks. >>>> >>>> Given this, I just want to step back a little to understand the exact >>>> issue that SGX is causing here for LSM/IMA. Sorry if I missed points >>>> discussed earlier. >>>> >>>> By the time of EADD, enclave file is opened and should have passed >>>> IMA and >>>> SELinux policy enforcement gates if any. We really don't need extra >>>> mmaps >>>> on the enclave files to be IMA and SELinux compliant. >>> >>> The problem, as i see it, is that they passed the *wrong* checks, >>> because, as you noticed: >>> >>>> We are loading >>>> enclave files as RO and copying those into EPC. >>> >>> Which is, semantically, a lot like loading a normal file as RO and >>> then mprotecting() it to RX, which is disallowed under quite a few LSM >>> policies. >>> >>>> An IMA policy can enforce >>>> RO files (or any file). And SELinux policy can say which processes can >>>> open the file for what permissions. No extra needed here. >>> >>> If SELinux says a process may open a file as RO, that does *not* mean >>> that it can be opened as RX. >>> >> >> But in this case, file itself is mapped as RO treated like data and it >> is not for execution. SGX enclave pages have EPCM enforced permissions. >> So from SELinux point of view I would think it can treat it as RO and >> that's fine. > > As an example, SELinux has an “execute” permission (via > security_mmap_file — see file_map_prot_check()) that controls whether > you can execute code from that file. If you lack this permission on a > file, you may still be able to map it PROT_READ, but you may not map > it PROT_EXEC. Similarly, if you want to malloc() some memory, write > *code* to it, and execute it, you need a specific permission. > > So, unless we somehow think it’s okay for SGX to break the existing > model, we need to respect these restrictions in the SGX driver. In > other words, we either need to respect execmem, etc or require > PROT_EXEC or the equivalent. I like the latter a lot more. What puzzles me is that this restriction does not add real value to security. When enclave files are mapped with PROT_READ, without SE execute permission. No breakage to LSM model in normal process address space as no one can execute code directly from the file in normal memory. When enclave is built and loaded into EPC by EADDs, if the SIGSTRUCT is trusted (either signer or MRENCLAVE), EINIT will guarantee security (both integrity and permissions). LSM may not like the fact the a piece of code got loaded into EPC page without specifically giving SE execute permission. However, LSM can be used to control what SIGSTRUCTs can be trusted as you suggested and indirectly enforce what code got executed inside EPC. So to me, only SIGSTRUCT verification would add value.
On Tue, May 14, 2019 at 1:45 PM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Tue, May 14, 2019 at 08:13:36AM -0700, Andy Lutomirski wrote: > > On Tue, May 14, 2019 at 3:43 AM Jarkko Sakkinen > > <jarkko.sakkinen@linux.intel.com> wrote: > > > > > > On Mon, May 13, 2019 at 01:29:26PM +0300, Jarkko Sakkinen wrote: > > > > I did study through SDK's file format and realized that it does not > > > > does make sense after all to embed one. > > > > > > > > To implement it properly you would probably need a new syscall (lets say > > > > sgx_load_enclave) and also that enclaves are not just executables > > > > binaries. It is hard to find a generic format for them as applications > > > > range from simply protecting part of an application to running a > > > > containter inside enclave. > > > > > > I'm still puzzling what kind of changes you were discussing considering > > > SGX_IOC_ENCLAVE_ADD_PAGE. > > > > I think it's as simple as requiring that, if SECINFO.X is set, then > > the src pointer points to the appropriate number of bytes of > > executable memory. (Unless there's some way for an enclave to change > > SECINFO after the fact -- is there?) > > Nit: SECINFO is just the struct passed to EADD, I think what you're really > asking is "can the EPCM permissions be changed after the fact". > > And the answer is, yes. > > On SGX2 hardware, the enclave can extend the EPCM permissions at runtime > via ENCLU[EMODPE], e.g. to make a page writable. > > Hardware also doesn't prevent doing EADD to the same virtual address > multiple times, e.g. an enclave could EADD a RX page, and then EADD a > RW page at the same virtual address with different data. The second EADD > will affect MRENCLAVE, but so long as it's accounted for by the enclave's > signer, it's "legal". SGX_IOC_ENCLAVE_ADD_PAGE *does* prevent adding the > "same" page to an enclave multiple times, so effectively this scenario is > blocked by the current implementation, but it's more of a side effect (of > a sane implementation) as opposed to deliberately preventing shenanigans. > > Regarding EMODPE, the kernel doesn't rely on EPCM permissions in any way > shape or form (the EPCM permissions are purely to protect the enclave > from the kernel), e.g. adding +X to a page in the EPCM doesn't magically > change the kernel's page tables and attempting to execute from the page > will still generate a (non-SGX) #PF. > > So rather than check SECINFO.X, I think we'd want to have EADD check that > the permissions in SECINFO are a subset of the VMA's perms (IIUC, this is > essentially what Cedric proposed). That would prevent using EMODPE to > gain executable permissions, and would explicitly deny the scenario of a > double EADD to load non-executable data into an executable page. Let me make sure I'm understanding this correctly: when an enclave tries to execute code, it only works if *both* the EPCM and the page tables grant the access, right? This seems to be that 37.3 is trying to say. So we should probably just ignore SECINFO for these purposes. But thinking this all through, it's a bit more complicated than any of this. Looking at the SELinux code for inspiration, there are quite a few paths, but they boil down to two cases: EXECUTE is the right to map an unmodified file executably, and EXECMOD/EXECMEM (the distinction seems mostly irrelevant) is the right to create (via mmap or mprotect) a modified anonymous file mapping or a non-file-backed mapping that is executable. So, if we do nothing, then mapping an enclave with execute permission will require either EXECUTE on the enclave inode or EXECMOD/EXECMEM, depending on exactly how this gets set up. So all is well, sort of. The problem is that I expect there will be people who want enclaves to work in a process that does not have these rights. To make this work, we probably need do some surgery on SELinux. ISTM the act of copying (via the EADD ioctl) data from a PROT_EXEC mapping to an enclave should not be construed as "modifying" the enclave for SELinux purposes. Actually doing this could be awkward, since the same inode will have executable parts and non-executable parts, and SELinux can't really tell the difference. Maybe the enclave should track a bitmap of which pages have ever been either mapped for write or EADDed with a *source* that wasn't PROT_EXEC. And then SELinux could learn to allow those pages (and only those pages) to be mapped executably without EXECUTE or EXECMOD or whatever permission. Does this seem at all reasonable? I suppose it's not the end of the world if the initially merged version doesn't do this, as long as there's some reasonable path to adding a mechanism like this when there's demand for it.
Hi Everyone, I think we are talking about 2 different kinds of criteria for determining the sanity of an enclave. The first kind determines an enclave's sanity by generally accepted good practices. For example, no executable pages shall ever be writable. The second kind determines an enclave's sanity by "who stands behind it", such as whether the file containing SIGSTRUCT has the proper SELinux label/type, or whether the signing key is trusted. I'd say those 2 kinds of criteria should be orthogonal because they don't get into each other's way and it could also be beneficial to enable both at the same time. For example, a user may want to allow launching enclaves signed by himself/herself only, however, as a human being he/she may make mistakes so would also like to ensure no RWX pages even for those enclaves explicitly authorized. I think those 2 kinds of criteria could be abstracted as 2 new LSM hooks - security_sgx_add_pages() and security_sgx_initialize_enclave(). security_sgx_add_pages() is invoked by SGX driver to determine if a range of source pages are allowed to be EADD'ed with the requested EPCM attributes, and by default it returns 0 (allowed) iff the requested EPCM attributes are a subset of the permissions of the VMA covering that range of source pages. An (SGX-aware) LSM module/policy could employ different criteria, such as making sure the source pages are backed by an enclave file (using SELinux label, for example). security_sgx_initialize_enclave() is invoked by SGX driver to determine if a given SIGSTRUCT is valid (hence allowed to be EINIT'ed), and it always returns 0 (allowed) by default. An LSM implementation may enforce custom policies, such as whether the signing public key is trusted by the current user, or whether it was backed (mmap()'ed) by an authorized file (e.g. of an expected type in the case of SELinux, or located in a particular directory in the case of AppArmor). With regard to SGX2/EDMM (Enclave Dynamic Memory Management) support, RW->RX transitions are inevitable to support certain usages such as dynamic loading/linking, meaning those usages may be blocked by existing policies. But from security perspective, I think they should be allowed by default (i.e. for non-SGX-aware LSM modules/policies) because such permission changes are inherent behaviors of the enclave itself, which is considered "sane" after passing all the checks performed in security_sgx_add_pages()/security_sgx_initialize_enclave(). Of course, an SGX-aware LSM module/policy shall be allowed to override. How about adding a new security_sgx_mprotect() LSM hook? > From: Andy Lutomirski [mailto:luto@kernel.org] > > > On May 14, 2019, at 8:30 AM, Haitao Huang > <haitao.huang@linux.intel.com> wrote: > > > >> On Tue, 14 May 2019 10:17:29 -0500, Andy Lutomirski <luto@kernel.org> > wrote: > >> > >> On Tue, May 14, 2019 at 7:33 AM Haitao Huang > >> <haitao.huang@linux.intel.com> wrote: > >>> > >>> On Fri, 10 May 2019 14:22:34 -0500, Andy Lutomirski > >>> <luto@kernel.org> > >>> wrote: > >>> > >>> > On Fri, May 10, 2019 at 12:04 PM Jethro Beekman > >>> > <jethro@fortanix.com> > >>> > wrote: > >>> >> > >>> >> On 2019-05-10 11:56, Xing, Cedric wrote: > >>> >> > Hi Jethro, > >>> >> > > >>> >> >> ELF files are explicitly designed such that you can map them > >>> >> >> (with > >>> >> mmap) > >>> >> >> in 4096-byte chunks. However, sometimes there's overlap and > >>> >> >> you will sometimes see that a particular offset is mapped > >>> >> >> twice because the > >>> >> first > >>> >> >> half of the page in the file belongs to an RX range and the > >>> >> >> second > >>> >> half > >>> >> >> to an R-only range. Also, ELF files don't (normally) describe > >>> >> >> stack, heap, etc. which you do need for enclaves. > >>> >> > > >>> >> > You have probably misread my email. By mmap(), I meant the > >>> >> > enclave > >>> >> file would be mapped via *multiple* mmap() calls, in the same way > >>> >> as what dlopen() would do in loading regular shared object. The > >>> >> intention here is to make the enclave file subject to the same > >>> >> checks as regular shared objects. > >>> >> > >>> >> No, I didn't misread your email. My original point still stands: > >>> >> requiring that an enclave's memory is created from one or more > >>> >> mmap calls of a file puts significant restrictions on the > >>> >> enclave's on-disk representation. > >>> >> > >>> > > >>> > For a tiny bit of background, Linux (AFAIK*) makes no effort to > >>> > ensure the complete integrity of DSOs. What Linux *does* do (if > >>> > so > >>> > configured) is to make sure that only approved data is mapped > >>> > executable. So, if you want to have some bytes be executable, > >>> > those bytes have to come from a file that passes the relevant LSM > >>> > and IMA checks. > >>> > >>> Given this, I just want to step back a little to understand the > >>> exact issue that SGX is causing here for LSM/IMA. Sorry if I missed > >>> points discussed earlier. > >>> > >>> By the time of EADD, enclave file is opened and should have passed > >>> IMA and SELinux policy enforcement gates if any. We really don't > >>> need extra mmaps on the enclave files to be IMA and SELinux > compliant. > >> > >> The problem, as i see it, is that they passed the *wrong* checks, > >> because, as you noticed: > >> > >>> We are loading > >>> enclave files as RO and copying those into EPC. > >> > >> Which is, semantically, a lot like loading a normal file as RO and > >> then mprotecting() it to RX, which is disallowed under quite a few > >> LSM policies. > >> > >>> An IMA policy can enforce > >>> RO files (or any file). And SELinux policy can say which processes > >>> can open the file for what permissions. No extra needed here. > >> > >> If SELinux says a process may open a file as RO, that does *not* mean > >> that it can be opened as RX. > >> > > > > But in this case, file itself is mapped as RO treated like data and it > is not for execution. SGX enclave pages have EPCM enforced permissions. > So from SELinux point of view I would think it can treat it as RO and > that's fine. > > As an example, SELinux has an “execute” permission (via > security_mmap_file — see file_map_prot_check()) that controls whether > you can execute code from that file. If you lack this permission on a > file, you may still be able to map it PROT_READ, but you may not map it > PROT_EXEC. Similarly, if you want to malloc() some memory, write > *code* to it, and execute it, you need a specific permission. > > So, unless we somehow think it’s okay for SGX to break the existing > model, we need to respect these restrictions in the SGX driver. In other > words, we either need to respect execmem, etc or require PROT_EXEC or > the equivalent. I like the latter a lot more. -Cedric
Hi Andy, > Let me make sure I'm understanding this correctly: when an enclave tries > to execute code, it only works if *both* the EPCM and the page tables > grant the access, right? This seems to be that 37.3 is trying to say. > So we should probably just ignore SECINFO for these purposes. > > But thinking this all through, it's a bit more complicated than any of > this. Looking at the SELinux code for inspiration, there are quite a > few paths, but they boil down to two cases: EXECUTE is the right to map > an unmodified file executably, and EXECMOD/EXECMEM (the distinction > seems mostly irrelevant) is the right to create (via mmap or mprotect) a > modified anonymous file mapping or a non-file-backed mapping that is > executable. So, if we do nothing, then mapping an enclave with execute > permission will require either EXECUTE on the enclave inode or > EXECMOD/EXECMEM, depending on exactly how this gets set up. > > So all is well, sort of. The problem is that I expect there will be > people who want enclaves to work in a process that does not have these > rights. To make this work, we probably need do some surgery on SELinux. > ISTM the act of copying (via the EADD ioctl) data from a PROT_EXEC > mapping to an enclave should not be construed as "modifying" > the enclave for SELinux purposes. Actually doing this could be awkward, > since the same inode will have executable parts and non-executable parts, > and SELinux can't really tell the difference. > Enclave files are pretty much like shared objects in that they both contain executable code mapped into the host process's address space. Do shared objects need EXECUTE to be mapped executable? If so, why would people not want EXECUTE in enclave files? Wrt to which part of an executable file is executable, the limitation resides in security_mmap_file(), which doesn't take the range of bytes as input. It isn't SGX specific. I'd say just let enclaves inherit applicable checks for shared objects. Then it will also inherit all future enhancements transparently. > Maybe the enclave should track a bitmap of which pages have ever been > either mapped for write or EADDed with a *source* that wasn't PROT_EXEC. > And then SELinux could learn to allow those pages (and only those pages) > to be mapped executably without EXECUTE or EXECMOD or whatever > permission. What do you mean by "enclave" here? The enclave range (ELRANGE) created by mmap()'ing /dev/sgx/enclave device? My argument is, an enclave's sanity/insanity is determined at load time (EADD/EEXTEND and EINIT) and all page accesses are enforced by EPCM, so PTE permissions really don't matter. As I discussed in an earlier email, I'd allow RWX for any range backed by /dev/sgx/enclave device file by default, unless an SGX-aware LSM module/policy objects to that (e.g. via a new security_sgx_mprotect() LSM hook). > > Does this seem at all reasonable? > > I suppose it's not the end of the world if the initially merged version > doesn't do this, as long as there's some reasonable path to adding a > mechanism like this when there's demand for it. Agreed! -Cedric
On Tue, May 14, 2019 at 02:27:08PM -0700, Andy Lutomirski wrote: > On Tue, May 14, 2019 at 1:45 PM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > On Tue, May 14, 2019 at 08:13:36AM -0700, Andy Lutomirski wrote: > > > On Tue, May 14, 2019 at 3:43 AM Jarkko Sakkinen > > > <jarkko.sakkinen@linux.intel.com> wrote: > > > > > > > > On Mon, May 13, 2019 at 01:29:26PM +0300, Jarkko Sakkinen wrote: > > > > > I did study through SDK's file format and realized that it does not > > > > > does make sense after all to embed one. > > > > > > > > > > To implement it properly you would probably need a new syscall (lets say > > > > > sgx_load_enclave) and also that enclaves are not just executables > > > > > binaries. It is hard to find a generic format for them as applications > > > > > range from simply protecting part of an application to running a > > > > > containter inside enclave. > > > > > > > > I'm still puzzling what kind of changes you were discussing considering > > > > SGX_IOC_ENCLAVE_ADD_PAGE. > > > > > > I think it's as simple as requiring that, if SECINFO.X is set, then > > > the src pointer points to the appropriate number of bytes of > > > executable memory. (Unless there's some way for an enclave to change > > > SECINFO after the fact -- is there?) > > > > Nit: SECINFO is just the struct passed to EADD, I think what you're really > > asking is "can the EPCM permissions be changed after the fact". > > > > And the answer is, yes. > > > > On SGX2 hardware, the enclave can extend the EPCM permissions at runtime > > via ENCLU[EMODPE], e.g. to make a page writable. > > > > Hardware also doesn't prevent doing EADD to the same virtual address > > multiple times, e.g. an enclave could EADD a RX page, and then EADD a > > RW page at the same virtual address with different data. The second EADD > > will affect MRENCLAVE, but so long as it's accounted for by the enclave's > > signer, it's "legal". SGX_IOC_ENCLAVE_ADD_PAGE *does* prevent adding the > > "same" page to an enclave multiple times, so effectively this scenario is > > blocked by the current implementation, but it's more of a side effect (of > > a sane implementation) as opposed to deliberately preventing shenanigans. > > > > Regarding EMODPE, the kernel doesn't rely on EPCM permissions in any way > > shape or form (the EPCM permissions are purely to protect the enclave > > from the kernel), e.g. adding +X to a page in the EPCM doesn't magically > > change the kernel's page tables and attempting to execute from the page > > will still generate a (non-SGX) #PF. > > > > So rather than check SECINFO.X, I think we'd want to have EADD check that > > the permissions in SECINFO are a subset of the VMA's perms (IIUC, this is > > essentially what Cedric proposed). That would prevent using EMODPE to > > gain executable permissions, and would explicitly deny the scenario of a > > double EADD to load non-executable data into an executable page. > > Let me make sure I'm understanding this correctly: when an enclave > tries to execute code, it only works if *both* the EPCM and the page > tables grant the access, right? This seems to be that 37.3 is trying > to say. So we should probably just ignore SECINFO for these purposes. Yep. More specifically, the EPCM is consulted if and only if the access is allowed by the page tables. I agree on ignoring SECINFO. > But thinking this all through, it's a bit more complicated than any of > this. Looking at the SELinux code for inspiration, there are quite a > few paths, but they boil down to two cases: EXECUTE is the right to > map an unmodified file executably, and EXECMOD/EXECMEM (the > distinction seems mostly irrelevant) is the right to create (via mmap > or mprotect) a modified anonymous file mapping or a non-file-backed > mapping that is executable. So, if we do nothing, then mapping an > enclave with execute permission will require either EXECUTE on the > enclave inode or EXECMOD/EXECMEM, depending on exactly how this gets > set up. If we do literally nothing, then I'm pretty sure mapping an enclave will require PROCESS__EXECMEM. The mmap() for the actual enclave is done using an anon inode, e.g. from /dev/sgx/enclave. Anon inodes are marked private, which means inode_has_perm() will always return "success". The only effective check is in file_map_prot_check() when default_noexec is true, in which case requesting PROT_EXEC on private inodes requires PROCESS__EXECMEM. > So all is well, sort of. The problem is that I expect there will be > people who want enclaves to work in a process that does not have these > rights. To make this work, we probably need do some surgery on > SELinux. ISTM the act of copying (via the EADD ioctl) data from a > PROT_EXEC mapping to an enclave should not be construed as "modifying" > the enclave for SELinux purposes. Actually doing this could be > awkward, since the same inode will have executable parts and > non-executable parts, and SELinux can't really tell the difference. Rather the do surgery on SELinux, why not go with Cedric's original proposal and propagate the permissions from the source VMA to the EPC VMA? The enclave mmap() from userspace could then be done with RO permissions so as to not run afoul of LSMs. Adding PROT_EXEC after EADD would require PROCESS__EXECMEM, but that's in line with mprotect() on regular memory. It also punts the EMODPE hiccup to userspace, e.g. any enclave that wants to do fancy things with PROT_EXEC needs PROCESS__EXECMEM. The only downside I see is that SGX would be doing a bit of magic with enclave VMAs. > Maybe the enclave should track a bitmap of which pages have ever been > either mapped for write or EADDed with a *source* that wasn't > PROT_EXEC. And then SELinux could learn to allow those pages (and > only those pages) to be mapped executably without EXECUTE or EXECMOD > or whatever permission. > > Does this seem at all reasonable? No? I don't understand why you want to special case enclaves from an LSM perspective. Enforcing LSM policies on the source provides the same security for enclaves as it does for normal code, no more, no less. > I suppose it's not the end of the world if the initially merged > version doesn't do this, as long as there's some reasonable path to > adding a mechanism like this when there's demand for it.
On Tue, 14 May 2019 16:58:24 -0500, Xing, Cedric <cedric.xing@intel.com> wrote: > Hi Everyone, > > I think we are talking about 2 different kinds of criteria for > determining the sanity of an enclave. > > The first kind determines an enclave's sanity by generally accepted good > practices. For example, no executable pages shall ever be writable. > We'll have to trust user space doing mmap with right permissions as SELinux does not enforce which segment to be RW and which to be RX. The file needs to have SELinux EXECUTE and WRITE both, if we need map some segments with RW and others with RX. We could say EINIT would ensure user is doing the right thing because it would fail if user map permission wrongly. Then the extra mmaps are redundant of doing SIGSTRUCT verification. Additionally, per Sean's comments, after EADD in current implementation, we will still need PROCESS_EXECMEM for mprotect on enclave fd to change some EPC pages PTE to RX before enclave can execute. So I don't think mmap the source enclave file would gain anything in addition to what your proposed security_sgx_initialize_enclave() does. Since security_sgx_initialize_enclave() is a lot like launch control policy enforcement we discussed a lot and resolved, I tend to agree with Andy's assessment we can just do nothing for the initial merge and add hooks needed if someone wants them. And the initial merge would require the enclave hosting processes ask for PROCESS_EXECMEM permission to do mmap/mprotect with enclave fd.
On Tue, May 14, 2019 at 08:08:03AM -0700, Andy Lutomirski wrote: > > Putting everything together, I'd suggest to: > > - Change EADD ioctl to take source page's VMA permission as ("upper bound" of) EPCM permission. This make sure no one can circumvent LSM to generate executable code on the fly using SGX driver. > > - Change EINIT ioctl to invoke (new?) LSM hook to validate SIGSTRUCT before issuing EINIT. > > I'm okay with this if the consensus is that having a .sigstruct file > is too annoying. SIGSTRUCT has two nice properties from kernel perspective: - Static structure - Fully defines enclave contents including the page permissions as they are part of the measurement. Making it as the "root of trust" really is the right thing and the most robust way to deal with this. /Jarkko
On Tue, May 14, 2019 at 08:13:36AM -0700, Andy Lutomirski wrote: > On Tue, May 14, 2019 at 3:43 AM Jarkko Sakkinen > <jarkko.sakkinen@linux.intel.com> wrote: > > > > On Mon, May 13, 2019 at 01:29:26PM +0300, Jarkko Sakkinen wrote: > > > I did study through SDK's file format and realized that it does not > > > does make sense after all to embed one. > > > > > > To implement it properly you would probably need a new syscall (lets say > > > sgx_load_enclave) and also that enclaves are not just executables > > > binaries. It is hard to find a generic format for them as applications > > > range from simply protecting part of an application to running a > > > containter inside enclave. > > > > I'm still puzzling what kind of changes you were discussing considering > > SGX_IOC_ENCLAVE_ADD_PAGE. > > I think it's as simple as requiring that, if SECINFO.X is set, then > the src pointer points to the appropriate number of bytes of > executable memory. (Unless there's some way for an enclave to change > SECINFO after the fact -- is there?) Sadly, we don't really have the > a nice in-kernel API for that right now. You could do > down_read(mmap_sem) and find_vma(). Arguably there is no value to > checking that PKRU allows execute to the data. OK, so you would actually go on to check whether the VMA where the data is copied contains executable data? What if SECINFO.X is not set and you EADD to region that is executable in the enclave VMA? E.g. have RWX VMA for the enclave just to give a simple example. Then you could carry executable code in the data sections of the binary. /Jarkko
On Wed, May 15, 2019 at 11:49:09AM +0300, Jarkko Sakkinen wrote: > On Tue, May 14, 2019 at 08:13:36AM -0700, Andy Lutomirski wrote: > > On Tue, May 14, 2019 at 3:43 AM Jarkko Sakkinen > > <jarkko.sakkinen@linux.intel.com> wrote: > > > > > > On Mon, May 13, 2019 at 01:29:26PM +0300, Jarkko Sakkinen wrote: > > > > I did study through SDK's file format and realized that it does not > > > > does make sense after all to embed one. > > > > > > > > To implement it properly you would probably need a new syscall (lets say > > > > sgx_load_enclave) and also that enclaves are not just executables > > > > binaries. It is hard to find a generic format for them as applications > > > > range from simply protecting part of an application to running a > > > > containter inside enclave. > > > > > > I'm still puzzling what kind of changes you were discussing considering > > > SGX_IOC_ENCLAVE_ADD_PAGE. > > > > I think it's as simple as requiring that, if SECINFO.X is set, then > > the src pointer points to the appropriate number of bytes of > > executable memory. (Unless there's some way for an enclave to change > > SECINFO after the fact -- is there?) Sadly, we don't really have the > > a nice in-kernel API for that right now. You could do > > down_read(mmap_sem) and find_vma(). Arguably there is no value to > > checking that PKRU allows execute to the data. > > OK, so you would actually go on to check whether the VMA where the data > is copied contains executable data? > > What if SECINFO.X is not set and you EADD to region that is executable > in the enclave VMA? E.g. have RWX VMA for the enclave just to give a > simple example. Then you could carry executable code in the data > sections of the binary. This would require to be done after the enclave is initialized of course with EMODPR, which requires the enclave to accept the permission change with EACCEPT, which limits somewhat. This means that the static enclave would be fully covered. /Jarkko
On Tue, May 14, 2019 at 01:45:27PM -0700, Sean Christopherson wrote: > On Tue, May 14, 2019 at 08:13:36AM -0700, Andy Lutomirski wrote: > > On Tue, May 14, 2019 at 3:43 AM Jarkko Sakkinen > > <jarkko.sakkinen@linux.intel.com> wrote: > > > > > > On Mon, May 13, 2019 at 01:29:26PM +0300, Jarkko Sakkinen wrote: > > > > I did study through SDK's file format and realized that it does not > > > > does make sense after all to embed one. > > > > > > > > To implement it properly you would probably need a new syscall (lets say > > > > sgx_load_enclave) and also that enclaves are not just executables > > > > binaries. It is hard to find a generic format for them as applications > > > > range from simply protecting part of an application to running a > > > > containter inside enclave. > > > > > > I'm still puzzling what kind of changes you were discussing considering > > > SGX_IOC_ENCLAVE_ADD_PAGE. > > > > I think it's as simple as requiring that, if SECINFO.X is set, then > > the src pointer points to the appropriate number of bytes of > > executable memory. (Unless there's some way for an enclave to change > > SECINFO after the fact -- is there?) > > Nit: SECINFO is just the struct passed to EADD, I think what you're really > asking is "can the EPCM permissions be changed after the fact". > > And the answer is, yes. > > On SGX2 hardware, the enclave can extend the EPCM permissions at runtime > via ENCLU[EMODPE], e.g. to make a page writable. Small correction: it is EMODPR. Anyway, it is good to mention that these would require EACCEPT from the enclave side. In order to take advantage of this is in a malicous enclave, one would require SELinux/IMA/whatnot policy to have permitted it in the first place. Thus, it cannot be said that it breaks the security policy if this would happen because policy has allowed to use the particular enclave. > Hardware also doesn't prevent doing EADD to the same virtual address > multiple times, e.g. an enclave could EADD a RX page, and then EADD a > RW page at the same virtual address with different data. The second EADD > will affect MRENCLAVE, but so long as it's accounted for by the enclave's > signer, it's "legal". SGX_IOC_ENCLAVE_ADD_PAGE *does* prevent adding the > "same" page to an enclave multiple times, so effectively this scenario is > blocked by the current implementation, but it's more of a side effect (of > a sane implementation) as opposed to deliberately preventing shenanigans. If the security policy can define who can create legit SIGSTRUCT files, this should not be a problem. Neither should be how EEXTEND is used. This brings me to an open question in Andy's model: lets say that we change the source for SIGSTRUCT from memory address to fd. How can the policy prevent the use not creating a file containing a SIGSTRUCT and passing fd of that to the EINIT ioctl? If we can sort this question out, then SIGSTRUCT-centered way to control enclave would actually be robust. /Jarkko
On Wed, May 15, 2019 at 01:35:31PM +0300, Jarkko Sakkinen wrote: > This brings me to an open question in Andy's model: lets say that we > change the source for SIGSTRUCT from memory address to fd. How can the > policy prevent the use not creating a file containing a SIGSTRUCT and > passing fd of that to the EINIT ioctl? Also wondering if a path would be better than plain fd for defining a reasonable policy i.e. have sigstruct_path as part of the ioctl parameters and not sigstruct_fd. /Jarkko
On Wed, May 15, 2019 at 01:35:31PM +0300, Jarkko Sakkinen wrote: > On Tue, May 14, 2019 at 01:45:27PM -0700, Sean Christopherson wrote: > > On Tue, May 14, 2019 at 08:13:36AM -0700, Andy Lutomirski wrote: > > > I think it's as simple as requiring that, if SECINFO.X is set, then > > > the src pointer points to the appropriate number of bytes of > > > executable memory. (Unless there's some way for an enclave to change > > > SECINFO after the fact -- is there?) > > > > Nit: SECINFO is just the struct passed to EADD, I think what you're really > > asking is "can the EPCM permissions be changed after the fact". > > > > And the answer is, yes. > > > > On SGX2 hardware, the enclave can extend the EPCM permissions at runtime > > via ENCLU[EMODPE], e.g. to make a page writable. > > Small correction: it is EMODPR. No, I'm referring to EMODPE, note the ENCLU classification. > Anyway, it is good to mention that these would require EACCEPT from the > enclave side. In order to take advantage of this is in a malicous > enclave, one would require SELinux/IMA/whatnot policy to have permitted > it in the first place. EMODPE doesn't require EACCEPT or any equivalent from the kernel. As you alluded to, the page tables would still need to allow PROT_EXEC. I was simply trying to answer Andy's question regarding SECINFO. > Thus, it cannot be said that it breaks the security policy if this would > happen because policy has allowed to use the particular enclave.
> On May 15, 2019, at 4:00 AM, Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > >> On Wed, May 15, 2019 at 01:35:31PM +0300, Jarkko Sakkinen wrote: >> This brings me to an open question in Andy's model: lets say that we >> change the source for SIGSTRUCT from memory address to fd. How can the >> policy prevent the use not creating a file containing a SIGSTRUCT and >> passing fd of that to the EINIT ioctl? > The policy will presumably check the label on the file that the fd points to. > Also wondering if a path would be better than plain fd for defining a > reasonable policy i.e. have sigstruct_path as part of the ioctl > parameters and not sigstruct_fd. > It would save two syscalls at the cost of a decent amount of complexity.
Hi, LSM and SELinux people- We're trying to figure out how SGX fits in with LSMs. For background, an SGX library is functionally a bit like a DSO, except that it's nominally resistant to attack from outside and the process of loading it is complicated. To load an enclave, a program can open /dev/sgx/enclave, do some ioctls to load the code and data segments into the enclave, call a special ioctl to "initialize" the enclave, and then call into the enclave (using special CPU instructions). One nastiness is that there is not actually a universally agreed upon, documented file format for enclaves. Windows has an undocumented format, and there are probably a few others out there. No one really wants to teach the kernel to parse enclave files. There are two issues with how this interacts with LSMs: 1) LSMs might want to be able to whitelist, blacklist, or otherwise restrict what enclaves can run at all. The current proposal that everyone seems to dislike the least is to have a .sigstruct file on disk that contains a hash and signature of the enclave in a CPU-defined format. To initialize an enclave, a program will pass an fd to this file, and a new LSM hook can be called to allow or disallow the operation. In a SELinux context, the idea is that policy could require the .sigstruct file to be labeled with a type like sgx_sigstruct_t, and only enclaves that have a matching .sigstruct with such a label could run. 2) Just like any other DSO, there are potential issues with how enclaves deal with writable vs executable memory. This takes two forms. First, a task should probably require EXECMEM, EXECMOD, or similar permission to run an enclave that can modify its own text. Second, it would be nice if a task that did *not* have EXECMEM, EXECMOD, or similar could still run the enclave if it had EXECUTE permission on the file containing the enclave. Currently, this all works because DSOs are run by mmapping the file to create multiple VMAs, some of which are executable, non-writable, and non-CoWed, and some of which are writable but not executable. With SGX, there's only really one inode per enclave (the anon_inode that comes form /dev/sgx/enclave), and it can only be sensibly mapped MAP_SHARED. With the current version of the SGX driver, to run an enclave, I think you'll need either EXECUTE rights to /dev/sgx/enclave or EXECMOD or similar, all of which more or less mean that you can run any modified code you want, and none of which is useful to prevent enclaves from contain RWX segments. So my question is: what, if anything, should change to make this work better? Here's a very vague proposal that's kind of like what I've been thinking over the past few days. The SGX inode could track, for each page, a "safe-to-execute" bit. When you first open /dev/sgx/enclave, you get a blank enclave and all pages are safe-to-execute. When you do the ioctl to load context (which could be code, data, or anything else), the kernel will check whether the *source* VMA is executable and, if not, mark the page of the enclave being loaded as unsafe. Once the enclave is initialized, the driver will clear the safe-to-execute bit for any page that is successfully mapped writably. The intent is that a page of the enclave is safe-to-execute if that page was populated from executable memory and not modified since then. LSMs could then enforce a policy that you can map an enclave page RX if the page is safe-to-execute, you can map any page you want for write if there are no executable mappings, and you can only map a page for write and execute simultaneously if you can EXECMOD permission. This should allow an enclave to be loaded by userspace from a file with EXECUTE rights. So here are my questions: Are the goals I mentioned reasonable? Is the design I just outlined reasonable? Would SELinux support this? Is there a better solution that works well enough? Thanks, all! > On May 14, 2019, at 6:30 PM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > >> But thinking this all through, it's a bit more complicated than any of >> this. Looking at the SELinux code for inspiration, there are quite a >> few paths, but they boil down to two cases: EXECUTE is the right to >> map an unmodified file executably, and EXECMOD/EXECMEM (the >> distinction seems mostly irrelevant) is the right to create (via mmap >> or mprotect) a modified anonymous file mapping or a non-file-backed >> mapping that is executable. So, if we do nothing, then mapping an >> enclave with execute permission will require either EXECUTE on the >> enclave inode or EXECMOD/EXECMEM, depending on exactly how this gets >> set up. > > If we do literally nothing, then I'm pretty sure mapping an enclave will > require PROCESS__EXECMEM. The mmap() for the actual enclave is done > using an anon inode, e.g. from /dev/sgx/enclave. Anon inodes are marked > private, which means inode_has_perm() will always return "success". The > only effective check is in file_map_prot_check() when default_noexec is > true, in which case requesting PROT_EXEC on private inodes requires > PROCESS__EXECMEM. > >> So all is well, sort of. The problem is that I expect there will be >> people who want enclaves to work in a process that does not have these >> rights. To make this work, we probably need do some surgery on >> SELinux. ISTM the act of copying (via the EADD ioctl) data from a >> PROT_EXEC mapping to an enclave should not be construed as "modifying" >> the enclave for SELinux purposes. Actually doing this could be >> awkward, since the same inode will have executable parts and >> non-executable parts, and SELinux can't really tell the difference. > > Rather the do surgery on SELinux, why not go with Cedric's original > proposal and propagate the permissions from the source VMA to the EPC > VMA? Which EPC VMA? Users can map the enclave fd again after EADD, resulting in a new VMA. And any realistic enclave will presumably have RO, RW, and RX pages. > The enclave mmap() from userspace could then be done with RO > permissions so as to not run afoul of LSMs. Adding PROT_EXEC after > EADD would require PROCESS__EXECMEM, but that's in line with mprotect() > on regular memory. How does this help anything? The driver currently only works with EXECMEM and, with this change, it still needs EXECMEM. I think that, if we’re going to make changes along these lines, the goal should be that you can have an enclave serialized in a file on disk such that you have EXECUTE on the file, and you should be able to load and run the enclave without needing EXECMEM. (Unless the enclave is self-modifying, of course.)
On Wed, 15 May 2019, Andy Lutomirski wrote: > There are two issues with how this interacts with LSMs: > > 1) LSMs might want to be able to whitelist, blacklist, or otherwise > restrict what enclaves can run at all. The current proposal that > everyone seems to dislike the least is to have a .sigstruct file on > disk that contains a hash and signature of the enclave in a > CPU-defined format. To initialize an enclave, a program will pass an > fd to this file, and a new LSM hook can be called to allow or disallow > the operation. In a SELinux context, the idea is that policy could > require the .sigstruct file to be labeled with a type like > sgx_sigstruct_t, and only enclaves that have a matching .sigstruct > with such a label could run. The .sigstruct file is for the CPU to consume, not the kernel correct? How is it bound to the enclave file? Why not just use an xattr, like security.sgx ? > > 2) Just like any other DSO, there are potential issues with how > enclaves deal with writable vs executable memory. This takes two > forms. First, a task should probably require EXECMEM, EXECMOD, or > similar permission to run an enclave that can modify its own text. > Second, it would be nice if a task that did *not* have EXECMEM, > EXECMOD, or similar could still run the enclave if it had EXECUTE > permission on the file containing the enclave. > > Currently, this all works because DSOs are run by mmapping the file to > create multiple VMAs, some of which are executable, non-writable, and > non-CoWed, and some of which are writable but not executable. With > SGX, there's only really one inode per enclave (the anon_inode that > comes form /dev/sgx/enclave), and it can only be sensibly mapped > MAP_SHARED. > > With the current version of the SGX driver, to run an enclave, I think > you'll need either EXECUTE rights to /dev/sgx/enclave or EXECMOD or > similar, all of which more or less mean that you can run any modified > code you want, and none of which is useful to prevent enclaves from > contain RWX segments. > > So my question is: what, if anything, should change to make this work better? Would it be possible to provide multiple fds (perhaps via a pseudo fs interface) which can be mapped to different types of VMAs?
On Wed, May 15, 2019 at 12:58 PM James Morris <jmorris@namei.org> wrote: > > On Wed, 15 May 2019, Andy Lutomirski wrote: > > > There are two issues with how this interacts with LSMs: > > > > 1) LSMs might want to be able to whitelist, blacklist, or otherwise > > restrict what enclaves can run at all. The current proposal that > > everyone seems to dislike the least is to have a .sigstruct file on > > disk that contains a hash and signature of the enclave in a > > CPU-defined format. To initialize an enclave, a program will pass an > > fd to this file, and a new LSM hook can be called to allow or disallow > > the operation. In a SELinux context, the idea is that policy could > > require the .sigstruct file to be labeled with a type like > > sgx_sigstruct_t, and only enclaves that have a matching .sigstruct > > with such a label could run. > > > The .sigstruct file is for the CPU to consume, not the kernel correct? Yes, unless an LSM wants to examine it to make a decision. > > How is it bound to the enclave file? It's not bound to the enclave *file* at all, but it contains a hash that covers the enclave, so two different files in two different formats representing exactly the same enclave would get the same hash, but any change to the enclave would get a different hash. > > Why not just use an xattr, like security.sgx ? Wouldn't this make it so that only someone with CAP_MAC_ADMIN could install an enclave? I think that this decision should be left up the administrator, and it should be easy to set up a loose policy where anyone can load whatever enclave they want. That's what would happen in my proposal if there was no LSM loaded or of the LSM policy didn't restrict what .sigstruct files were acceptable. > > > > > 2) Just like any other DSO, there are potential issues with how > > enclaves deal with writable vs executable memory. This takes two > > forms. First, a task should probably require EXECMEM, EXECMOD, or > > similar permission to run an enclave that can modify its own text. > > Second, it would be nice if a task that did *not* have EXECMEM, > > EXECMOD, or similar could still run the enclave if it had EXECUTE > > permission on the file containing the enclave. > > > > Currently, this all works because DSOs are run by mmapping the file to > > create multiple VMAs, some of which are executable, non-writable, and > > non-CoWed, and some of which are writable but not executable. With > > SGX, there's only really one inode per enclave (the anon_inode that > > comes form /dev/sgx/enclave), and it can only be sensibly mapped > > MAP_SHARED. > > > > With the current version of the SGX driver, to run an enclave, I think > > you'll need either EXECUTE rights to /dev/sgx/enclave or EXECMOD or > > similar, all of which more or less mean that you can run any modified > > code you want, and none of which is useful to prevent enclaves from > > contain RWX segments. > > > > So my question is: what, if anything, should change to make this work better? > > Would it be possible to provide multiple fds (perhaps via a pseudo fs > interface) which can be mapped to different types of VMAs? Maybe. The tricky bit is that, even if there was a separate inode for the writable and the executable parts of the enclave, I think that both would have to be mapped MAP_SHARED since MAP_ANONYMOUS is nonsensical for SGX. This would certainly push more complexity into the user code. Jarkko?
On Wed, May 15, 2019 at 11:27:04AM -0700, Andy Lutomirski wrote: > 2) Just like any other DSO, there are potential issues with how > enclaves deal with writable vs executable memory. This takes two > forms. First, a task should probably require EXECMEM, EXECMOD, or > similar permission to run an enclave that can modify its own text. > Second, it would be nice if a task that did *not* have EXECMEM, > EXECMOD, or similar could still run the enclave if it had EXECUTE > permission on the file containing the enclave. > > Currently, this all works because DSOs are run by mmapping the file to > create multiple VMAs, some of which are executable, non-writable, and > non-CoWed, and some of which are writable but not executable. With > SGX, there's only really one inode per enclave (the anon_inode that > comes form /dev/sgx/enclave), and it can only be sensibly mapped > MAP_SHARED. I was wrong when I said /dev/sgx/enclave creates and returns an anon inode. I was thinking of the KVM model for creating VMs. SGX creates an enclave when /dev/sgx/enclave is opened and associates the enclave with the newly opened /dev/sgx/enclave fd. Regardless, the fundamental problem remains, mmap() of EPC works on a single inode.
On Wed, 15 May 2019, Andy Lutomirski wrote: > > Why not just use an xattr, like security.sgx ? > > Wouldn't this make it so that only someone with CAP_MAC_ADMIN could > install an enclave? I think that this decision should be left up the > administrator, and it should be easy to set up a loose policy where > anyone can load whatever enclave they want. That's what would happen > in my proposal if there was no LSM loaded or of the LSM policy didn't > restrict what .sigstruct files were acceptable. > You could try user.sigstruct, which does not require any privs.
On Wed, May 15, 2019 at 3:46 PM James Morris <jmorris@namei.org> wrote: > > On Wed, 15 May 2019, Andy Lutomirski wrote: > > > > Why not just use an xattr, like security.sgx ? > > > > Wouldn't this make it so that only someone with CAP_MAC_ADMIN could > > install an enclave? I think that this decision should be left up the > > administrator, and it should be easy to set up a loose policy where > > anyone can load whatever enclave they want. That's what would happen > > in my proposal if there was no LSM loaded or of the LSM policy didn't > > restrict what .sigstruct files were acceptable. > > > > You could try user.sigstruct, which does not require any privs. > I don't think I understand your proposal. What file would this attribute be on? What would consume it? I'm imagining that there's some enclave in a file crypto_thingy.enclave. There's also a file crypto_thingy.sigstruct. crypto_thingy.enclave has type lib_t or similar so that it's executable. crypto_thingy.sigstruct has type sgx_sigstruct_t. The enclave loader does, in effect: void *source_data = mmap(crypto_thingy.enclave, PROT_READ | PROT_EXEC, ...); int sigstruct_fd = open("crypto_thingy.sigstruct", O_RDONLY); int enclave_fd = open("/dev/sgx/enclave", O_RDWR); ioctl(enclave_fd, SGX_IOC_ADD_SOME_DATA, source_data + source_offset, enclave_offset, len, ...); ioctl(enclave_fd, SGX_IOC_ADD_SOME_DATA, source_data + source_offset2, enclave_offset2, len, ...); etc. /* Here's where LSMs get to check that the sigstruct is acceptable. The CPU will check that the sigstruct matches the enclave. */ ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, sigstruct_fd); /* Actually map the thing */ mmap(enclave_fd RO section, PROT_READ, ...); mmap(enclave_fd RW section, PROT_READ | PROT_WRITE, ...); mmap(enclave_fd RX section, PROT_READ | PROT_EXEC, ...); /* This should fail unless EXECMOD is available, I think */ mmap(enclave_fd RWX section, PROT_READ | PROT_WRITE | PROT_EXEC); And the idea here is that, if the .enclave file isn't mapped PROT_EXEC, then mmapping the RX section will also require EXECMEM or EXECMOD.
On Wed, 15 May 2019 16:38:58 -0500, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > On Wed, May 15, 2019 at 11:27:04AM -0700, Andy Lutomirski wrote: >> 2) Just like any other DSO, there are potential issues with how >> enclaves deal with writable vs executable memory. This takes two >> forms. First, a task should probably require EXECMEM, EXECMOD, or >> similar permission to run an enclave that can modify its own text. >> Second, it would be nice if a task that did *not* have EXECMEM, >> EXECMOD, or similar could still run the enclave if it had EXECUTE >> permission on the file containing the enclave. >> >> Currently, this all works because DSOs are run by mmapping the file to >> create multiple VMAs, some of which are executable, non-writable, and >> non-CoWed, and some of which are writable but not executable. With >> SGX, there's only really one inode per enclave (the anon_inode that >> comes form /dev/sgx/enclave), and it can only be sensibly mapped >> MAP_SHARED. > > I was wrong when I said /dev/sgx/enclave creates and returns an anon > inode. I was thinking of the KVM model for creating VMs. SGX creates > an enclave when /dev/sgx/enclave is opened and associates the enclave > with the newly opened /dev/sgx/enclave fd. > > Regardless, the fundamental problem remains, mmap() of EPC works on a > single inode. If I read code in file_map_prot_check() correctly, only when you request W+X at the same time that EXECMEM would be required for MAP_SHARED, right? If so, I believe SGX enclaves would never need that.
Hi Andy, > From: Andy Lutomirski [mailto:luto@kernel.org] > > On Wed, May 15, 2019 at 3:46 PM James Morris <jmorris@namei.org> wrote: > > > > On Wed, 15 May 2019, Andy Lutomirski wrote: > > > > > > Why not just use an xattr, like security.sgx ? > > > > > > Wouldn't this make it so that only someone with CAP_MAC_ADMIN could > > > install an enclave? I think that this decision should be left up the > > > administrator, and it should be easy to set up a loose policy where > > > anyone can load whatever enclave they want. That's what would happen > > > in my proposal if there was no LSM loaded or of the LSM policy didn't > > > restrict what .sigstruct files were acceptable. > > > > > > > You could try user.sigstruct, which does not require any privs. > > > > I don't think I understand your proposal. What file would this > attribute be on? What would consume it? > > I'm imagining that there's some enclave in a file > crypto_thingy.enclave. There's also a file crypto_thingy.sigstruct. > crypto_thingy.enclave has type lib_t or similar so that it's > executable. crypto_thingy.sigstruct has type sgx_sigstruct_t. The > enclave loader does, in effect: > > void *source_data = mmap(crypto_thingy.enclave, PROT_READ | PROT_EXEC, ...); > int sigstruct_fd = open("crypto_thingy.sigstruct", O_RDONLY); > int enclave_fd = open("/dev/sgx/enclave", O_RDWR); > > ioctl(enclave_fd, SGX_IOC_ADD_SOME_DATA, source_data + source_offset, > enclave_offset, len, ...); > ioctl(enclave_fd, SGX_IOC_ADD_SOME_DATA, source_data + source_offset2, > enclave_offset2, len, ...); > etc. > > /* Here's where LSMs get to check that the sigstruct is acceptable. > The CPU will check that the sigstruct matches the enclave. */ > ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, sigstruct_fd); SIGSTRUCT isn't necessarily stored on disk so may not always have a fd. How about the following? void *ss_pointer = mmap(sigstruct_fd, PROT_READ,...); ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, ss_pointer); The idea here is SIGSTRUCT will still be passed in memory so it works the same way when no LSM modules are loaded or basing its decision on the .sigstruct file. Otherwise, an LSM module can figure out the backing file (and offset within that file) by looking into the VMA covering ss_pointer. > > /* Actually map the thing */ > mmap(enclave_fd RO section, PROT_READ, ...); > mmap(enclave_fd RW section, PROT_READ | PROT_WRITE, ...); > mmap(enclave_fd RX section, PROT_READ | PROT_EXEC, ...); > > /* This should fail unless EXECMOD is available, I think */ > mmap(enclave_fd RWX section, PROT_READ | PROT_WRITE | PROT_EXEC); > > And the idea here is that, if the .enclave file isn't mapped > PROT_EXEC, then mmapping the RX section will also require EXECMEM or > EXECMOD. From security perspective, I think it reasonable to give EXECMEM and EXECMOD to /dev/sgx/enclave because the actual permissions are guarded by EPCM permissions, which are "inherited" from the source pages, whose permissions have passed LSM checks. Alternatively, I think we could mark enclave VMAs somewhat differently, such as defining a new VM_SGX flag. The reason behind that is, enclave ranges differ from "regular" virtual ranges in terms of both functionality (i.e. #PF will have to be handled quite differently) and security, so I believe demand will come up to distinguish them eventually - e.g., LSM modules can then enforce different policies on them (by a new security_sgx_mprot() hook?). -Cedric
> On May 15, 2019, at 8:03 PM, Xing, Cedric <cedric.xing@intel.com> wrote: > > Hi Andy, > >> From: Andy Lutomirski [mailto:luto@kernel.org] >> >>> On Wed, May 15, 2019 at 3:46 PM James Morris <jmorris@namei.org> wrote: >>> >>> On Wed, 15 May 2019, Andy Lutomirski wrote: >>> >>>>> Why not just use an xattr, like security.sgx ? >>>> >>>> Wouldn't this make it so that only someone with CAP_MAC_ADMIN could >>>> install an enclave? I think that this decision should be left up the >>>> administrator, and it should be easy to set up a loose policy where >>>> anyone can load whatever enclave they want. That's what would happen >>>> in my proposal if there was no LSM loaded or of the LSM policy didn't >>>> restrict what .sigstruct files were acceptable. >>>> >>> >>> You could try user.sigstruct, which does not require any privs. >>> >> >> I don't think I understand your proposal. What file would this >> attribute be on? What would consume it? >> >> I'm imagining that there's some enclave in a file >> crypto_thingy.enclave. There's also a file crypto_thingy.sigstruct. >> crypto_thingy.enclave has type lib_t or similar so that it's >> executable. crypto_thingy.sigstruct has type sgx_sigstruct_t. The >> enclave loader does, in effect: >> >> void *source_data = mmap(crypto_thingy.enclave, PROT_READ | PROT_EXEC, ...); >> int sigstruct_fd = open("crypto_thingy.sigstruct", O_RDONLY); >> int enclave_fd = open("/dev/sgx/enclave", O_RDWR); >> >> ioctl(enclave_fd, SGX_IOC_ADD_SOME_DATA, source_data + source_offset, >> enclave_offset, len, ...); >> ioctl(enclave_fd, SGX_IOC_ADD_SOME_DATA, source_data + source_offset2, >> enclave_offset2, len, ...); >> etc. >> >> /* Here's where LSMs get to check that the sigstruct is acceptable. >> The CPU will check that the sigstruct matches the enclave. */ >> ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, sigstruct_fd); > > SIGSTRUCT isn't necessarily stored on disk so may not always have a fd. How about the following? > void *ss_pointer = mmap(sigstruct_fd, PROT_READ,...); > ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, ss_pointer); > > The idea here is SIGSTRUCT will still be passed in memory so it works the same way when no LSM modules are loaded or basing its decision on the .sigstruct file. Otherwise, an LSM module can figure out the backing file (and offset within that file) by looking into the VMA covering ss_pointer. I don’t love this approach. Application authors seem likely to use read() instead of mmap(), and it’ll still work in many cares. It would also complicate the kernel implementation, and looking at the inode backing the vma that backs a pointer is at least rather unusual. Instead, if the sigstruct isn’t on disk because it’s dynamic or came from a network, the application can put it in a memfd. > >> >> /* Actually map the thing */ >> mmap(enclave_fd RO section, PROT_READ, ...); >> mmap(enclave_fd RW section, PROT_READ | PROT_WRITE, ...); >> mmap(enclave_fd RX section, PROT_READ | PROT_EXEC, ...); >> >> /* This should fail unless EXECMOD is available, I think */ >> mmap(enclave_fd RWX section, PROT_READ | PROT_WRITE | PROT_EXEC); >> >> And the idea here is that, if the .enclave file isn't mapped >> PROT_EXEC, then mmapping the RX section will also require EXECMEM or >> EXECMOD. > > From security perspective, I think it reasonable to give EXECMEM and EXECMOD to /dev/sgx/enclave because the actual permissions are guarded by EPCM permissions, which are "inherited" from the source pages, whose permissions have passed LSM checks. I disagree. If you deny a program EXECMOD, it’s not because you distrust the program. It’s because you want to enforce good security practices. (Or you’re Apple and want to disallow third-party JITs.) A policy that accepts any sigstruct but requires that enclaves come from disk and respect W^X seems entirely reasonable. I think that blocking EXECMOD has likely served two very real security purposes. It helps force application and library developers to write and compile their code in a way that doesn’t rely on dangerous tricks like putting executable trampolines on the stack. It also makes it essentially impossible for an exploit to run actual downloaded machine code — if there is no way to run code that isn’t appropriately labeled, then attackers are more limited in what they can do. I don’t think that SGX should become an exception to either of these. Code should not have an excuse to use WX memory just because it’s in an enclave. Similarly, an exploit should not be able to run an attacker-supplied enclave as a way around a policy that would otherwise prevent downloaded code from running. —Andy
On Wed, May 15, 2019 at 06:21:47AM -0700, Sean Christopherson wrote: > On Wed, May 15, 2019 at 01:35:31PM +0300, Jarkko Sakkinen wrote: > > On Tue, May 14, 2019 at 01:45:27PM -0700, Sean Christopherson wrote: > > > On Tue, May 14, 2019 at 08:13:36AM -0700, Andy Lutomirski wrote: > > > > I think it's as simple as requiring that, if SECINFO.X is set, then > > > > the src pointer points to the appropriate number of bytes of > > > > executable memory. (Unless there's some way for an enclave to change > > > > SECINFO after the fact -- is there?) > > > > > > Nit: SECINFO is just the struct passed to EADD, I think what you're really > > > asking is "can the EPCM permissions be changed after the fact". > > > > > > And the answer is, yes. > > > > > > On SGX2 hardware, the enclave can extend the EPCM permissions at runtime > > > via ENCLU[EMODPE], e.g. to make a page writable. > > > > Small correction: it is EMODPR. > > No, I'm referring to EMODPE, note the ENCLU classification. > > > Anyway, it is good to mention that these would require EACCEPT from the > > enclave side. In order to take advantage of this is in a malicous > > enclave, one would require SELinux/IMA/whatnot policy to have permitted > > it in the first place. > > EMODPE doesn't require EACCEPT or any equivalent from the kernel. As > you alluded to, the page tables would still need to allow PROT_EXEC. I > was simply trying to answer Andy's question regarding SECINFO. Ah, have to admit that I had totally forgot EMODPE :-) Have not had to deal with that opcode that much. /Jarkko
On Wed, May 15, 2019 at 07:27:02AM -0700, Andy Lutomirski wrote: > > > On May 15, 2019, at 4:00 AM, Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > > > >> On Wed, May 15, 2019 at 01:35:31PM +0300, Jarkko Sakkinen wrote: > >> This brings me to an open question in Andy's model: lets say that we > >> change the source for SIGSTRUCT from memory address to fd. How can the > >> policy prevent the use not creating a file containing a SIGSTRUCT and > >> passing fd of that to the EINIT ioctl? > > > > The policy will presumably check the label on the file that the fd points to. Right (checked SELinux documentation). Got one idea from this. Right now creation and initialization does not require any VMAs to be created (since v20). Requiring to map a VMA for copying the data would bring in my opinion a glitch to this model that we have done effort to build up. What if we similarly change EADD ioctl in a way that it'd take an fd and an offset? This way we can enforce policy to the source where the enclave data is loaded from. On the other hand, loading SIGSTRUCT from fd enforces a legit structure for the enclave. This would still allow to construct enclaves in VMA independent way. > > Also wondering if a path would be better than plain fd for defining a > > reasonable policy i.e. have sigstruct_path as part of the ioctl > > parameters and not sigstruct_fd. > > > > It would save two syscalls at the cost of a decent amount of complexity. And also using fd gives robustness because it allows SIGSTRUCT pulled from a file containing it (among other things). /Jarkko
On Wed, May 15, 2019 at 11:27:04AM -0700, Andy Lutomirski wrote: > Hi, LSM and SELinux people- > > We're trying to figure out how SGX fits in with LSMs. For background, > an SGX library is functionally a bit like a DSO, except that it's > nominally resistant to attack from outside and the process of loading > it is complicated. To load an enclave, a program can open > /dev/sgx/enclave, do some ioctls to load the code and data segments > into the enclave, call a special ioctl to "initialize" the enclave, > and then call into the enclave (using special CPU instructions). > > One nastiness is that there is not actually a universally agreed upon, > documented file format for enclaves. Windows has an undocumented > format, and there are probably a few others out there. No one really > wants to teach the kernel to parse enclave files. > > There are two issues with how this interacts with LSMs: > > 1) LSMs might want to be able to whitelist, blacklist, or otherwise > restrict what enclaves can run at all. The current proposal that > everyone seems to dislike the least is to have a .sigstruct file on > disk that contains a hash and signature of the enclave in a > CPU-defined format. To initialize an enclave, a program will pass an > fd to this file, and a new LSM hook can be called to allow or disallow > the operation. In a SELinux context, the idea is that policy could > require the .sigstruct file to be labeled with a type like > sgx_sigstruct_t, and only enclaves that have a matching .sigstruct > with such a label could run. Similarly if we could take data for the enclave from fd and enforce it with sgx_enclave_t label. > Here's a very vague proposal that's kind of like what I've been > thinking over the past few days. The SGX inode could track, for each > page, a "safe-to-execute" bit. When you first open /dev/sgx/enclave, > you get a blank enclave and all pages are safe-to-execute. When you > do the ioctl to load context (which could be code, data, or anything > else), the kernel will check whether the *source* VMA is executable > and, if not, mark the page of the enclave being loaded as unsafe. > Once the enclave is initialized, the driver will clear the > safe-to-execute bit for any page that is successfully mapped writably. With the fd based model for source I'd mark SECINFO.W pages as unsafe to execute and then check unsafe bit before applying lets say EMODT or EMODPR. There is a problem here though. Usually the enclave itself is just a loader that then loads the application from outside source and creates the executable pages from the content. A great example of this is Graphene that bootstraps unmodified Linux applications to an enclave: https://github.com/oscarlab/graphene /Jarkko
On Thu, May 16, 2019 at 08:07:05AM +0300, Jarkko Sakkinen wrote: > On Wed, May 15, 2019 at 07:27:02AM -0700, Andy Lutomirski wrote: > > > > > On May 15, 2019, at 4:00 AM, Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > > > > > >> On Wed, May 15, 2019 at 01:35:31PM +0300, Jarkko Sakkinen wrote: > > >> This brings me to an open question in Andy's model: lets say that we > > >> change the source for SIGSTRUCT from memory address to fd. How can the > > >> policy prevent the use not creating a file containing a SIGSTRUCT and > > >> passing fd of that to the EINIT ioctl? > > > > > > > The policy will presumably check the label on the file that the fd points to. > > Right (checked SELinux documentation). > > Got one idea from this. Right now creation and initialization does not > require any VMAs to be created (since v20). Requiring to map a VMA for > copying the data would bring in my opinion a glitch to this model that > we have done effort to build up. > > What if we similarly change EADD ioctl in a way that it'd take an fd > and an offset? This way we can enforce policy to the source where the > enclave data is loaded from. On the other hand, loading SIGSTRUCT from > fd enforces a legit structure for the enclave. > > This would still allow to construct enclaves in VMA independent way. The API would turn into this: /** * struct sgx_enclave_add_page - parameter structure for the * %SGX_IOC_ENCLAVE_ADD_PAGE ioctl * @fd: file containing the page data * @offset: offset in the file containing the page data * @secinfo: address for the SECINFO data * @mrmask: bitmask for the measured 256 byte chunks */ struct sgx_enclave_add_page { __u64 fd; __u64 offset; __u64 secinfo; __u16 mrmask; } __attribute__((__packed__)); /** * struct sgx_enclave_init - parameter structure for the * %SGX_IOC_ENCLAVE_INIT ioctl * @fd: file containing the sigstruct * @offset: offset in the file containing the sigstruct */ struct sgx_enclave_init { __u64 fd; __u64 offset; }; /Jarkko
On Thu, May 16, 2019 at 09:51:03AM +0300, Jarkko Sakkinen wrote: > On Thu, May 16, 2019 at 08:07:05AM +0300, Jarkko Sakkinen wrote: > > On Wed, May 15, 2019 at 07:27:02AM -0700, Andy Lutomirski wrote: > > > > > > > On May 15, 2019, at 4:00 AM, Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > > > > > > > >> On Wed, May 15, 2019 at 01:35:31PM +0300, Jarkko Sakkinen wrote: > > > >> This brings me to an open question in Andy's model: lets say that we > > > >> change the source for SIGSTRUCT from memory address to fd. How can the > > > >> policy prevent the use not creating a file containing a SIGSTRUCT and > > > >> passing fd of that to the EINIT ioctl? > > > > > > > > > > The policy will presumably check the label on the file that the fd points to. > > > > Right (checked SELinux documentation). > > > > Got one idea from this. Right now creation and initialization does not > > require any VMAs to be created (since v20). Requiring to map a VMA for > > copying the data would bring in my opinion a glitch to this model that > > we have done effort to build up. > > > > What if we similarly change EADD ioctl in a way that it'd take an fd > > and an offset? This way we can enforce policy to the source where the > > enclave data is loaded from. On the other hand, loading SIGSTRUCT from > > fd enforces a legit structure for the enclave. > > > > This would still allow to construct enclaves in VMA independent way. > > The API would turn into this: > > /** > * struct sgx_enclave_add_page - parameter structure for the > * %SGX_IOC_ENCLAVE_ADD_PAGE ioctl > * @fd: file containing the page data > * @offset: offset in the file containing the page data > * @secinfo: address for the SECINFO data > * @mrmask: bitmask for the measured 256 byte chunks > */ > struct sgx_enclave_add_page { > __u64 fd; > __u64 offset; > __u64 secinfo; > __u16 mrmask; > } __attribute__((__packed__)); > > > /** > * struct sgx_enclave_init - parameter structure for the > * %SGX_IOC_ENCLAVE_INIT ioctl > * @fd: file containing the sigstruct > * @offset: offset in the file containing the sigstruct > */ > struct sgx_enclave_init { > __u64 fd; > __u64 offset; > }; The change to EADD/EINIT ioctl's would be simply fget/kernel_read/fput sequence replacing copy_from_user(). /Jarkko
On Wed, 15 May 2019, Andy Lutomirski wrote: > On Wed, May 15, 2019 at 3:46 PM James Morris <jmorris@namei.org> wrote: > > > > You could try user.sigstruct, which does not require any privs. > > > > I don't think I understand your proposal. What file would this > attribute be on? What would consume it? It would be on the enclave file, so you keep the sigstruct bound to it, rather than needing a separate file to manage. It would simplify any LSM policy check. It would be consumed by (I guess) the SGX_INIT_THE_ENCLAVE ioctl in your example, instead of having a 2nd fd.
> On May 16, 2019, at 12:24 AM, James Morris <jmorris@namei.org> wrote: > >> On Wed, 15 May 2019, Andy Lutomirski wrote: >> >>> On Wed, May 15, 2019 at 3:46 PM James Morris <jmorris@namei.org> wrote: >>> >>> You could try user.sigstruct, which does not require any privs. >>> >> >> I don't think I understand your proposal. What file would this >> attribute be on? What would consume it? > > It would be on the enclave file, so you keep the sigstruct bound to it, > rather than needing a separate file to manage. It would simplify any LSM > policy check. > > It would be consumed by (I guess) the SGX_INIT_THE_ENCLAVE ioctl in your > example, instead of having a 2nd fd. > > Okay, I think I see what you’re suggesting. I don’t think it works well, though, since loading the data from the enclave file will almost always be done in multiple chunks, and it’s not clear when the kernel should look for the xattr or what to do if the xattr changes part way through.
> On May 15, 2019, at 10:16 PM, Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > >> On Wed, May 15, 2019 at 11:27:04AM -0700, Andy Lutomirski wrote: >> Hi, LSM and SELinux people- >> >> We're trying to figure out how SGX fits in with LSMs. For background, >> an SGX library is functionally a bit like a DSO, except that it's >> nominally resistant to attack from outside and the process of loading >> it is complicated. To load an enclave, a program can open >> /dev/sgx/enclave, do some ioctls to load the code and data segments >> into the enclave, call a special ioctl to "initialize" the enclave, >> and then call into the enclave (using special CPU instructions). >> >> One nastiness is that there is not actually a universally agreed upon, >> documented file format for enclaves. Windows has an undocumented >> format, and there are probably a few others out there. No one really >> wants to teach the kernel to parse enclave files. >> >> There are two issues with how this interacts with LSMs: >> >> 1) LSMs might want to be able to whitelist, blacklist, or otherwise >> restrict what enclaves can run at all. The current proposal that >> everyone seems to dislike the least is to have a .sigstruct file on >> disk that contains a hash and signature of the enclave in a >> CPU-defined format. To initialize an enclave, a program will pass an >> fd to this file, and a new LSM hook can be called to allow or disallow >> the operation. In a SELinux context, the idea is that policy could >> require the .sigstruct file to be labeled with a type like >> sgx_sigstruct_t, and only enclaves that have a matching .sigstruct >> with such a label could run. > > Similarly if we could take data for the enclave from fd and enforce > it with sgx_enclave_t label. That certainly *could* be done, and I guess the decision could be left to the LSMs, but I'm not convinced this adds value. What security use case does this cover that isn't already covered by requiring EXECUTE (e.g. lib_t) on the enclave file and some new SIGSTRUCT right on the .sigstruct? > >> Here's a very vague proposal that's kind of like what I've been >> thinking over the past few days. The SGX inode could track, for each >> page, a "safe-to-execute" bit. When you first open /dev/sgx/enclave, >> you get a blank enclave and all pages are safe-to-execute. When you >> do the ioctl to load context (which could be code, data, or anything >> else), the kernel will check whether the *source* VMA is executable >> and, if not, mark the page of the enclave being loaded as unsafe. >> Once the enclave is initialized, the driver will clear the >> safe-to-execute bit for any page that is successfully mapped writably. > > With the fd based model for source I'd mark SECINFO.W pages as unsafe > to execute and then check unsafe bit before applying lets say EMODT > or EMODPR. > > There is a problem here though. Usually the enclave itself is just a > loader that then loads the application from outside source and creates > the executable pages from the content. > > A great example of this is Graphene that bootstraps unmodified Linux > applications to an enclave: > > https://github.com/oscarlab/graphene > ISTM you should need EXECMEM or similar to run Graphene, then.
Hi Andy, > > SIGSTRUCT isn't necessarily stored on disk so may not always have a fd. > How about the following? > > void *ss_pointer = mmap(sigstruct_fd, PROT_READ,...); > > ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, ss_pointer); > > > > The idea here is SIGSTRUCT will still be passed in memory so it works > the same way when no LSM modules are loaded or basing its decision on > the .sigstruct file. Otherwise, an LSM module can figure out the backing > file (and offset within that file) by looking into the VMA covering > ss_pointer. > > I don’t love this approach. Application authors seem likely to use > read() instead of mmap(), and it’ll still work in many cares. It would > also complicate the kernel implementation, and looking at the inode > backing the vma that backs a pointer is at least rather unusual. > Instead, if the sigstruct isn’t on disk because it’s dynamic or came > from a network, the application can put it in a memfd. I understand your concern here. But I guess we are making too much assumption on how enclaves are structured/packaged. My concern is, what if a SIGSTRUCT really has to be from memory? For example, an enclave (along with its SIGSTRUCT) could be embedded inside a shared object (or even the "main" executable) so it shows up in memory to begin with. Of course it could be copied to a memfd but whatever "attributes" (e.g. path, or SELinux class/type) associated with the original file would be lost, so I'm not sure if that would work. I'm also with you that applications tend to use read() instead of mmap() for accessing files. But in our case that'd be necessary only if .sigstruct is a separate file (hence needs to be read separately). What if (and I guess most implementations would) the SIGSTRUCT is embedded in the same file as the enclave? mmap() is the more common practice when dealing with executable images, and in that case SIGSTRUCT will have already been mmap()'d. I'm with you again that it's kind of unprecedented to look at the backing inode. But I believe we should strive to allow as large variety of applications/usages as possible and I don't see any alternatives without losing flexibility. > > > >> > >> /* Actually map the thing */ > >> mmap(enclave_fd RO section, PROT_READ, ...); > >> mmap(enclave_fd RW section, PROT_READ | PROT_WRITE, ...); > >> mmap(enclave_fd RX section, PROT_READ | PROT_EXEC, ...); > >> > >> /* This should fail unless EXECMOD is available, I think */ > >> mmap(enclave_fd RWX section, PROT_READ | PROT_WRITE | PROT_EXEC); > >> > >> And the idea here is that, if the .enclave file isn't mapped > >> PROT_EXEC, then mmapping the RX section will also require EXECMEM or > >> EXECMOD. > > > > From security perspective, I think it reasonable to give EXECMEM and > EXECMOD to /dev/sgx/enclave because the actual permissions are guarded > by EPCM permissions, which are "inherited" from the source pages, whose > permissions have passed LSM checks. > > I disagree. If you deny a program EXECMOD, it’s not because you > distrust the program. It’s because you want to enforce good security > practices. (Or you’re Apple and want to disallow third-party JITs.) > A policy that accepts any sigstruct but requires that enclaves come > from disk and respect W^X seems entirely reasonable. > > I think that blocking EXECMOD has likely served two very real security > purposes. It helps force application and library developers to write > and compile their code in a way that doesn’t rely on dangerous tricks > like putting executable trampolines on the stack. It also makes it > essentially impossible for an exploit to run actual downloaded machine > code — if there is no way to run code that isn’t appropriately > labeled, then attackers are more limited in what they can do. > > I don’t think that SGX should become an exception to either of these. > Code should not have an excuse to use WX memory just because it’s in > an enclave. Similarly, an exploit should not be able to run an > attacker-supplied enclave as a way around a policy that would > otherwise prevent downloaded code from running. My apology for the confusion here. I thought EXECMOD applied to files (and memory mappings backed by them) but I was probably wrong. It sounds like EXECMOD applies to the whole process so would allow all pages within a process's address space to be modified then executed, regardless the backing files. Am I correct this time? I was not saying enclaves were exempt to good security practices. What I was trying to say was, EPC pages are *not* subject to the same attacks as regular pages so I suspect there will be a desire to enforce different policies on them, especially after new SGX2 features/applications become available. So I think it beneficial to distinguish between regular vs. enclave virtual ranges. And to do that, a new VM_SGX flag in VMA is probably a very simple/easy way. And with that VM_SGX flag, we could add a new security_sgx_mprot() hook so that LSM modules/policies could act differently. And if you are with me on that bigger picture, the next question is: what should be the default behavior of security_sgx_mprot() for existing/non-SGX-aware LSM modules/policies? I'd say a reasonable default is to allow R, RW and RX, but not anything else. It'd suffice to get rid of EXECMEM/EXECMOD requirements on enclave applications. For SGX1, EPCM permissions are immutable so it really doesn't matter what security_sgx_mprot() does. For SGX2 and beyond, there's still time and new SGX-aware LSM modules/policies will probably have emerged by then. -Cedric
On Thu, May 16, 2019 at 02:02:58PM -0700, Andy Lutomirski wrote: > > On May 15, 2019, at 10:16 PM, Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > > There is a problem here though. Usually the enclave itself is just a > > loader that then loads the application from outside source and creates > > the executable pages from the content. > > > > A great example of this is Graphene that bootstraps unmodified Linux > > applications to an enclave: > > > > https://github.com/oscarlab/graphene > > > > ISTM you should need EXECMEM or similar to run Graphene, then. Agreed, Graphene is effectively running arbitrary enclave code. I'm guessing there is nothing that prevents extending/reworking Graphene to allow generating the enclave ahead of time so as to avoid populating the guts of the enclave at runtime, i.e. it's likely possible to run an unmodified application in an enclave without EXECMEM if that's something Graphene or its users really care about.
> > > There is a problem here though. Usually the enclave itself is just a > > > loader that then loads the application from outside source and > > > creates the executable pages from the content. > > > > > > A great example of this is Graphene that bootstraps unmodified Linux > > > applications to an enclave: > > > > > > https://github.com/oscarlab/graphene > > > > > > > ISTM you should need EXECMEM or similar to run Graphene, then. > > Agreed, Graphene is effectively running arbitrary enclave code. I'm > guessing there is nothing that prevents extending/reworking Graphene to > allow generating the enclave ahead of time so as to avoid populating the > guts of the enclave at runtime, i.e. it's likely possible to run an > unmodified application in an enclave without EXECMEM if that's something > Graphene or its users really care about. Inefficient use of memory is a problem of running Graphene on SGX1, from at least 2 aspects: 1) heaps/stacks have to be pre-allocated but only a small portion of those pages will be actually used; and 2) dynamic linking is commonly used in *unmodified* applications and all dependent libraries have to be loaded, but only a subset of those pages will actually be used - e.g. most applications use only a small set of functions in libc.so but the whole library still has to be loaded. Hence a practical/efficient solution will require/involve EDMM features available in SGX2. I guess we shall look a bit further into future in order to address this problem properly. And I think it necessary to distinguish enclave virtual ranges from regular ones (probably at VMA level) before we could have a practical solution.
On Wed, May 15, 2019 at 11:27:04AM -0700, Andy Lutomirski wrote: > Here's a very vague proposal that's kind of like what I've been > thinking over the past few days. The SGX inode could track, for each > page, a "safe-to-execute" bit. When you first open /dev/sgx/enclave, > you get a blank enclave and all pages are safe-to-execute. When you > do the ioctl to load context (which could be code, data, or anything > else), the kernel will check whether the *source* VMA is executable > and, if not, mark the page of the enclave being loaded as unsafe. > Once the enclave is initialized, the driver will clear the > safe-to-execute bit for any page that is successfully mapped writably. > > The intent is that a page of the enclave is safe-to-execute if that > page was populated from executable memory and not modified since then. > LSMs could then enforce a policy that you can map an enclave page RX > if the page is safe-to-execute, you can map any page you want for > write if there are no executable mappings, and you can only map a page > for write and execute simultaneously if you can EXECMOD permission. > This should allow an enclave to be loaded by userspace from a file > with EXECUTE rights. I'm still confused as to why you want to track execute permissions on the enclave pages and add SGX-specific LSM hooks. Is there anything that prevents userspace from building the enclave like any other DSO and then copying it into enclave memory? I feel like I'm missing something. 1. Userspace loads enclave into regular memory, e.g. like a normal DSO. All mmap(), mprotect(), etc... calls are subject to all existing LSM policies. 2. Userspace opens /dev/sgx/enclave to instantiate a new enclave. 3. Userspace uses mmap() to allocate virtual memory for its enclave, again subject to all existing LSM policies (sane userspaces map it RO since the permissions eventually get tossed anyways). 4. SGX subsystem refuses to service page faults for enclaves that have not yet been initialized, e.g. signals SIGBUS or SIGSEGV. 5. Userspace invokes SGX ioctl() to copy enclave from regulary VMA to enclave VMA. 6. SGX ioctl() propagates VMA protection-related flags from source VMA to enclave VMA, e.g. invokes mprotect_fixup(). Enclave VMA(s) may be split as part of this process. 7. At all times, mprotect() calls on the enclave VMA are subject to existing LSM policies, i.e. it's not special cased for enclaves. The SGX ioctl() would need to take mmap_sem for write, but we can mitigate that issue by changing the ioctl() to take a range of memory instead of a single page. That'd also provide "EADD batching" that folks have requested.
On Thu, May 16, 2019 at 5:03 PM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Wed, May 15, 2019 at 11:27:04AM -0700, Andy Lutomirski wrote: > > Here's a very vague proposal that's kind of like what I've been > > thinking over the past few days. The SGX inode could track, for each > > page, a "safe-to-execute" bit. When you first open /dev/sgx/enclave, > > you get a blank enclave and all pages are safe-to-execute. When you > > do the ioctl to load context (which could be code, data, or anything > > else), the kernel will check whether the *source* VMA is executable > > and, if not, mark the page of the enclave being loaded as unsafe. > > Once the enclave is initialized, the driver will clear the > > safe-to-execute bit for any page that is successfully mapped writably. > > > > The intent is that a page of the enclave is safe-to-execute if that > > page was populated from executable memory and not modified since then. > > LSMs could then enforce a policy that you can map an enclave page RX > > if the page is safe-to-execute, you can map any page you want for > > write if there are no executable mappings, and you can only map a page > > for write and execute simultaneously if you can EXECMOD permission. > > This should allow an enclave to be loaded by userspace from a file > > with EXECUTE rights. > > I'm still confused as to why you want to track execute permissions on the > enclave pages and add SGX-specific LSM hooks. Is there anything that > prevents userspace from building the enclave like any other DSO and then > copying it into enclave memory? It's entirely possible that I'm the one missing something. But here's why I think this: > I feel like I'm missing something. > > 1. Userspace loads enclave into regular memory, e.g. like a normal DSO. > All mmap(), mprotect(), etc... calls are subject to all existing > LSM policies. > > 2. Userspace opens /dev/sgx/enclave to instantiate a new enclave. > > 3. Userspace uses mmap() to allocate virtual memory for its enclave, > again subject to all existing LSM policies (sane userspaces map it RO > since the permissions eventually get tossed anyways). Is userspace actually requred to mmap() the enclave prior to EADDing things? > > 4. SGX subsystem refuses to service page faults for enclaves that have > not yet been initialized, e.g. signals SIGBUS or SIGSEGV. > > 5. Userspace invokes SGX ioctl() to copy enclave from regulary VMA to > enclave VMA. > > 6. SGX ioctl() propagates VMA protection-related flags from source VMA > to enclave VMA, e.g. invokes mprotect_fixup(). Enclave VMA(s) may > be split as part of this process. Does this also call the LSM? If so, what is it expected to do? What happens if there are different regions with different permissions on the same page? SGX has 256-byte granularity right? > > 7. At all times, mprotect() calls on the enclave VMA are subject to > existing LSM policies, i.e. it's not special cased for enclaves. I don't think the normal behavior actually works here. An enclave is always MAP_SHARED, so (with SELinux) mprotecting() to X or RX requires EXECUTE and mprotecting() to RWX requires extra permissions. But user code can also mmap() the enclave again. What is supposed to happen in that case?
On Thu, May 16, 2019 at 3:23 PM Xing, Cedric <cedric.xing@intel.com> wrote: > > Hi Andy, > > > > SIGSTRUCT isn't necessarily stored on disk so may not always have a fd. > > How about the following? > > > void *ss_pointer = mmap(sigstruct_fd, PROT_READ,...); > > > ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, ss_pointer); > > > > > > The idea here is SIGSTRUCT will still be passed in memory so it works > > the same way when no LSM modules are loaded or basing its decision on > > the .sigstruct file. Otherwise, an LSM module can figure out the backing > > file (and offset within that file) by looking into the VMA covering > > ss_pointer. > > > > I don’t love this approach. Application authors seem likely to use > > read() instead of mmap(), and it’ll still work in many cares. It would > > also complicate the kernel implementation, and looking at the inode > > backing the vma that backs a pointer is at least rather unusual. > > Instead, if the sigstruct isn’t on disk because it’s dynamic or came > > from a network, the application can put it in a memfd. > > I understand your concern here. But I guess we are making too much assumption on how enclaves are structured/packaged. My concern is, what if a SIGSTRUCT really has to be from memory? For example, an enclave (along with its SIGSTRUCT) could be embedded inside a shared object (or even the "main" executable) so it shows up in memory to begin with. Hmm. That's a fair point, although opening /proc/self/exe could be somewhat of a workaround. It does suffer from a bit of an in-band signaling problem, though, in that it's possible that some other random bytes in the library resemble a SIGSTRUCT. > I was not saying enclaves were exempt to good security practices. What I was trying to say was, EPC pages are *not* subject to the same attacks as regular pages so I suspect there will be a desire to enforce different policies on them, especially after new SGX2 features/applications become available. So I think it beneficial to distinguish between regular vs. enclave virtual ranges. And to do that, a new VM_SGX flag in VMA is probably a very simple/easy way. And with that VM_SGX flag, we could add a new security_sgx_mprot() hook so that LSM modules/policies could act differently. I'm not opposed to this, but I also don't think this needs to be in the initial upstream driver. VM_SGX also isn't strictly necessary -- an LSM could inspect the VMA to decide whether it's an SGX VMA if it really wanted to. That being said, do you have any specific behavior differences in mind aside from the oddities involved in loading the enclave. > > And if you are with me on that bigger picture, the next question is: what should be the default behavior of security_sgx_mprot() for existing/non-SGX-aware LSM modules/policies? I'd say a reasonable default is to allow R, RW and RX, but not anything else. It'd suffice to get rid of EXECMEM/EXECMOD requirements on enclave applications. For SGX1, EPCM permissions are immutable so it really doesn't matter what security_sgx_mprot() does. For SGX2 and beyond, there's still time and new SGX-aware LSM modules/policies will probably have emerged by then. I hadn't thought about the SGX1 vs SGX2 difference. If the driver initially only wants to support SGX1, then I guess we really could get away with constraining the EPC flags based on the source page permission and not restricting mprotect() and mmap() permissions on /dev/sgx/enclave at all.
> From: Andy Lutomirski [mailto:luto@kernel.org] > > On Thu, May 16, 2019 at 3:23 PM Xing, Cedric <cedric.xing@intel.com> > wrote: > > > > Hi Andy, > > > > > > SIGSTRUCT isn't necessarily stored on disk so may not always have > a fd. > > > How about the following? > > > > void *ss_pointer = mmap(sigstruct_fd, PROT_READ,...); > > > > ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, ss_pointer); > > > > > > > > The idea here is SIGSTRUCT will still be passed in memory so it > > > > works > > > the same way when no LSM modules are loaded or basing its decision > > > on the .sigstruct file. Otherwise, an LSM module can figure out the > > > backing file (and offset within that file) by looking into the VMA > > > covering ss_pointer. > > > > > > I don’t love this approach. Application authors seem likely to use > > > read() instead of mmap(), and it’ll still work in many cares. It > > > would also complicate the kernel implementation, and looking at the > > > inode backing the vma that backs a pointer is at least rather > unusual. > > > Instead, if the sigstruct isn’t on disk because it’s dynamic or came > > > from a network, the application can put it in a memfd. > > > > I understand your concern here. But I guess we are making too much > assumption on how enclaves are structured/packaged. My concern is, what > if a SIGSTRUCT really has to be from memory? For example, an enclave > (along with its SIGSTRUCT) could be embedded inside a shared object (or > even the "main" executable) so it shows up in memory to begin with. > > Hmm. That's a fair point, although opening /proc/self/exe could be > somewhat of a workaround. It does suffer from a bit of an in-band > signaling problem, though, in that it's possible that some other random > bytes in the library resemble a SIGSTRUCT. > > > I was not saying enclaves were exempt to good security practices. What > I was trying to say was, EPC pages are *not* subject to the same attacks > as regular pages so I suspect there will be a desire to enforce > different policies on them, especially after new SGX2 > features/applications become available. So I think it beneficial to > distinguish between regular vs. enclave virtual ranges. And to do that, > a new VM_SGX flag in VMA is probably a very simple/easy way. And with > that VM_SGX flag, we could add a new security_sgx_mprot() hook so that > LSM modules/policies could act differently. > > I'm not opposed to this, but I also don't think this needs to be in the > initial upstream driver. VM_SGX also isn't strictly necessary -- an LSM > could inspect the VMA to decide whether it's an SGX VMA if it really > wanted to. VM_SGX is just what I think is the easiest way for any module to tell enclave VMAs from all others. I agree totally with you that doesn't have to be in the initial release! > > That being said, do you have any specific behavior differences in mind > aside from the oddities involved in loading the enclave. The major thing is dynamically linked enclaves. Say if you want something like dlopen() inside an enclave, the driver would need to EAUG a page as RW initially, and then change to RX after it has been EACCEPTCOPY'ed by the enclave. So it's like a RW->RX transition and an LSM module/policy may want to allow it only if it's within an enclave range (ELRANGE), or deny it otherwise. > > > > > And if you are with me on that bigger picture, the next question is: > what should be the default behavior of security_sgx_mprot() for > existing/non-SGX-aware LSM modules/policies? I'd say a reasonable > default is to allow R, RW and RX, but not anything else. It'd suffice to > get rid of EXECMEM/EXECMOD requirements on enclave applications. For > SGX1, EPCM permissions are immutable so it really doesn't matter what > security_sgx_mprot() does. For SGX2 and beyond, there's still time and > new SGX-aware LSM modules/policies will probably have emerged by then. > > I hadn't thought about the SGX1 vs SGX2 difference. If the driver > initially only wants to support SGX1, then I guess we really could get > away with constraining the EPC flags based on the source page permission > and not restricting mprotect() and mmap() permissions on > /dev/sgx/enclave at all. This is exactly what I'm going after! But I have to apologize for this silly question because I don't know much about SELinux: Wouldn't it require changes to existing SELinux policies to *not* restrict mprotect() on /dev/sgx/enclave? -Cedric
On Thu, May 16, 2019 at 6:06 PM Xing, Cedric <cedric.xing@intel.com> wrote: > > > From: Andy Lutomirski [mailto:luto@kernel.org] > > > > On Thu, May 16, 2019 at 3:23 PM Xing, Cedric <cedric.xing@intel.com> > > wrote: > > > > > > Hi Andy, > > > > > > > > SIGSTRUCT isn't necessarily stored on disk so may not always have > > a fd. > > > > How about the following? > > > > > void *ss_pointer = mmap(sigstruct_fd, PROT_READ,...); > > > > > ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, ss_pointer); > > > > > > > > > > The idea here is SIGSTRUCT will still be passed in memory so it > > > > > works > > > > the same way when no LSM modules are loaded or basing its decision > > > > on the .sigstruct file. Otherwise, an LSM module can figure out the > > > > backing file (and offset within that file) by looking into the VMA > > > > covering ss_pointer. > > > > > > > > I don’t love this approach. Application authors seem likely to use > > > > read() instead of mmap(), and it’ll still work in many cares. It > > > > would also complicate the kernel implementation, and looking at the > > > > inode backing the vma that backs a pointer is at least rather > > unusual. > > > > Instead, if the sigstruct isn’t on disk because it’s dynamic or came > > > > from a network, the application can put it in a memfd. > > > > > > I understand your concern here. But I guess we are making too much > > assumption on how enclaves are structured/packaged. My concern is, what > > if a SIGSTRUCT really has to be from memory? For example, an enclave > > (along with its SIGSTRUCT) could be embedded inside a shared object (or > > even the "main" executable) so it shows up in memory to begin with. > > > > Hmm. That's a fair point, although opening /proc/self/exe could be > > somewhat of a workaround. It does suffer from a bit of an in-band > > signaling problem, though, in that it's possible that some other random > > bytes in the library resemble a SIGSTRUCT. > > > > > I was not saying enclaves were exempt to good security practices. What > > I was trying to say was, EPC pages are *not* subject to the same attacks > > as regular pages so I suspect there will be a desire to enforce > > different policies on them, especially after new SGX2 > > features/applications become available. So I think it beneficial to > > distinguish between regular vs. enclave virtual ranges. And to do that, > > a new VM_SGX flag in VMA is probably a very simple/easy way. And with > > that VM_SGX flag, we could add a new security_sgx_mprot() hook so that > > LSM modules/policies could act differently. > > > > I'm not opposed to this, but I also don't think this needs to be in the > > initial upstream driver. VM_SGX also isn't strictly necessary -- an LSM > > could inspect the VMA to decide whether it's an SGX VMA if it really > > wanted to. > > VM_SGX is just what I think is the easiest way for any module to tell enclave VMAs from all others. I agree totally with you that doesn't have to be in the initial release! > > > > > That being said, do you have any specific behavior differences in mind > > aside from the oddities involved in loading the enclave. > > The major thing is dynamically linked enclaves. Say if you want something like dlopen() inside an enclave, the driver would need to EAUG a page as RW initially, and then change to RX after it has been EACCEPTCOPY'ed by the enclave. So it's like a RW->RX transition and an LSM module/policy may want to allow it only if it's within an enclave range (ELRANGE), or deny it otherwise. I'm not convinced. Given that the kernel has no way to tell that the dynamically loaded code wasn't dynamically generated, I don't think it makes sense to allow this in an enclave but disallow it outside an enclave. > > > > > > > > > And if you are with me on that bigger picture, the next question is: > > what should be the default behavior of security_sgx_mprot() for > > existing/non-SGX-aware LSM modules/policies? I'd say a reasonable > > default is to allow R, RW and RX, but not anything else. It'd suffice to > > get rid of EXECMEM/EXECMOD requirements on enclave applications. For > > SGX1, EPCM permissions are immutable so it really doesn't matter what > > security_sgx_mprot() does. For SGX2 and beyond, there's still time and > > new SGX-aware LSM modules/policies will probably have emerged by then. > > > > I hadn't thought about the SGX1 vs SGX2 difference. If the driver > > initially only wants to support SGX1, then I guess we really could get > > away with constraining the EPC flags based on the source page permission > > and not restricting mprotect() and mmap() permissions on > > /dev/sgx/enclave at all. > > This is exactly what I'm going after! > > But I have to apologize for this silly question because I don't know much about SELinux: Wouldn't it require changes to existing SELinux policies to *not* restrict mprotect() on /dev/sgx/enclave? I'm assuming we'd make a small in-kernel change to SELinux to make it work without policy changes, assuming the SELinux maintainers would be okay with this.
On 5/16/19 6:23 PM, Xing, Cedric wrote: > Hi Andy, > >>> SIGSTRUCT isn't necessarily stored on disk so may not always have a fd. >> How about the following? >>> void *ss_pointer = mmap(sigstruct_fd, PROT_READ,...); >>> ioctl(enclave_fd, SGX_INIT_THE_ENCLAVE, ss_pointer); >>> >>> The idea here is SIGSTRUCT will still be passed in memory so it works >> the same way when no LSM modules are loaded or basing its decision on >> the .sigstruct file. Otherwise, an LSM module can figure out the backing >> file (and offset within that file) by looking into the VMA covering >> ss_pointer. >> >> I don’t love this approach. Application authors seem likely to use >> read() instead of mmap(), and it’ll still work in many cares. It would >> also complicate the kernel implementation, and looking at the inode >> backing the vma that backs a pointer is at least rather unusual. >> Instead, if the sigstruct isn’t on disk because it’s dynamic or came >> from a network, the application can put it in a memfd. > > I understand your concern here. But I guess we are making too much assumption on how enclaves are structured/packaged. My concern is, what if a SIGSTRUCT really has to be from memory? For example, an enclave (along with its SIGSTRUCT) could be embedded inside a shared object (or even the "main" executable) so it shows up in memory to begin with. Of course it could be copied to a memfd but whatever "attributes" (e.g. path, or SELinux class/type) associated with the original file would be lost, so I'm not sure if that would work. > > I'm also with you that applications tend to use read() instead of mmap() for accessing files. But in our case that'd be necessary only if .sigstruct is a separate file (hence needs to be read separately). What if (and I guess most implementations would) the SIGSTRUCT is embedded in the same file as the enclave? mmap() is the more common practice when dealing with executable images, and in that case SIGSTRUCT will have already been mmap()'d. > > I'm with you again that it's kind of unprecedented to look at the backing inode. But I believe we should strive to allow as large variety of applications/usages as possible and I don't see any alternatives without losing flexibility. > >>> >>>> >>>> /* Actually map the thing */ >>>> mmap(enclave_fd RO section, PROT_READ, ...); >>>> mmap(enclave_fd RW section, PROT_READ | PROT_WRITE, ...); >>>> mmap(enclave_fd RX section, PROT_READ | PROT_EXEC, ...); >>>> >>>> /* This should fail unless EXECMOD is available, I think */ >>>> mmap(enclave_fd RWX section, PROT_READ | PROT_WRITE | PROT_EXEC); >>>> >>>> And the idea here is that, if the .enclave file isn't mapped >>>> PROT_EXEC, then mmapping the RX section will also require EXECMEM or >>>> EXECMOD. >>> >>> From security perspective, I think it reasonable to give EXECMEM and >> EXECMOD to /dev/sgx/enclave because the actual permissions are guarded >> by EPCM permissions, which are "inherited" from the source pages, whose >> permissions have passed LSM checks. >> >> I disagree. If you deny a program EXECMOD, it’s not because you >> distrust the program. It’s because you want to enforce good security >> practices. (Or you’re Apple and want to disallow third-party JITs.) >> A policy that accepts any sigstruct but requires that enclaves come >> from disk and respect W^X seems entirely reasonable. >> >> I think that blocking EXECMOD has likely served two very real security >> purposes. It helps force application and library developers to write >> and compile their code in a way that doesn’t rely on dangerous tricks >> like putting executable trampolines on the stack. It also makes it >> essentially impossible for an exploit to run actual downloaded machine >> code — if there is no way to run code that isn’t appropriately >> labeled, then attackers are more limited in what they can do. > >> >> I don’t think that SGX should become an exception to either of these. >> Code should not have an excuse to use WX memory just because it’s in >> an enclave. Similarly, an exploit should not be able to run an >> attacker-supplied enclave as a way around a policy that would >> otherwise prevent downloaded code from running. > > My apology for the confusion here. > > I thought EXECMOD applied to files (and memory mappings backed by them) but I was probably wrong. It sounds like EXECMOD applies to the whole process so would allow all pages within a process's address space to be modified then executed, regardless the backing files. Am I correct this time? No, you were correct the first time I think; EXECMOD is used to control whether a process can make executable a private file mapping that has previously been modified (e.g. text relocation); it is a special case to support text relocations without having to allow full EXECMEM (i.e. execute arbitrary memory). SELinux checks relevant to W^X include: - EXECMEM: mmap/mprotect PROT_EXEC an anonymous mapping (regardless of PROT_WRITE, since we know the content has to have been written at some point) or a private file mapping that is also PROT_WRITE. - EXECMOD: mprotect PROT_EXEC a private file mapping that has been previously modified, typically for text relocations, - FILE__WRITE: mmap/mprotect PROT_WRITE a shared file mapping, - FILE__EXECUTE: mmap/mprotect PROT_EXEC a file mapping. (ignoring EXECSTACK and EXECHEAP here since they aren't really relevant to this discussion) So if you want to ensure W^X, then you wouldn't allow EXECMEM for the process, EXECMOD by the process to any file, and the combination of both FILE__WRITE and FILE__EXECUTE by the process to any file. If the /dev/sgx/enclave mappings are MAP_SHARED and you aren't using an anonymous inode, then I would expect that only the FILE__WRITE and FILE__EXECUTE checks are relevant. > > I was not saying enclaves were exempt to good security practices. What I was trying to say was, EPC pages are *not* subject to the same attacks as regular pages so I suspect there will be a desire to enforce different policies on them, especially after new SGX2 features/applications become available. So I think it beneficial to distinguish between regular vs. enclave virtual ranges. And to do that, a new VM_SGX flag in VMA is probably a very simple/easy way. And with that VM_SGX flag, we could add a new security_sgx_mprot() hook so that LSM modules/policies could act differently. > > And if you are with me on that bigger picture, the next question is: what should be the default behavior of security_sgx_mprot() for existing/non-SGX-aware LSM modules/policies? I'd say a reasonable default is to allow R, RW and RX, but not anything else. It'd suffice to get rid of EXECMEM/EXECMOD requirements on enclave applications. For SGX1, EPCM permissions are immutable so it really doesn't matter what security_sgx_mprot() does. For SGX2 and beyond, there's still time and new SGX-aware LSM modules/policies will probably have emerged by then. > > -Cedric >
On Fri, May 17, 2019 at 09:53:06AM -0400, Stephen Smalley wrote: > On 5/16/19 6:23 PM, Xing, Cedric wrote: > >I thought EXECMOD applied to files (and memory mappings backed by them) but > >I was probably wrong. It sounds like EXECMOD applies to the whole process so > >would allow all pages within a process's address space to be modified then > >executed, regardless the backing files. Am I correct this time? > > No, you were correct the first time I think; EXECMOD is used to control > whether a process can make executable a private file mapping that has > previously been modified (e.g. text relocation); it is a special case to > support text relocations without having to allow full EXECMEM (i.e. execute > arbitrary memory). > > SELinux checks relevant to W^X include: > > - EXECMEM: mmap/mprotect PROT_EXEC an anonymous mapping (regardless of > PROT_WRITE, since we know the content has to have been written at some > point) or a private file mapping that is also PROT_WRITE. > - EXECMOD: mprotect PROT_EXEC a private file mapping that has been > previously modified, typically for text relocations, > - FILE__WRITE: mmap/mprotect PROT_WRITE a shared file mapping, > - FILE__EXECUTE: mmap/mprotect PROT_EXEC a file mapping. > > (ignoring EXECSTACK and EXECHEAP here since they aren't really relevant to > this discussion) > > So if you want to ensure W^X, then you wouldn't allow EXECMEM for the > process, EXECMOD by the process to any file, and the combination of both > FILE__WRITE and FILE__EXECUTE by the process to any file. > > If the /dev/sgx/enclave mappings are MAP_SHARED and you aren't using an > anonymous inode, then I would expect that only the FILE__WRITE and > FILE__EXECUTE checks are relevant. Yep, I was just typing this up in a different thread: I think we may want to change the SGX API to alloc an anon inode for each enclave instead of hanging every enclave off of the /dev/sgx/enclave inode. Because /dev/sgx/enclave is NOT private, SELinux's file_map_prot_check() will only require FILE__WRITE and FILE__EXECUTE to mprotect() enclave VMAs to RWX. Backing each enclave with an anon inode will make SELinux treat EPC memory like anonymous mappings, which is what we want (I think), e.g. making *any* EPC page executable will require PROCESS__EXECMEM (SGX is 64-bit only at this point, so SELinux will always have default_noexec).
On Thu, May 16, 2019 at 05:26:15PM -0700, Andy Lutomirski wrote: > On Thu, May 16, 2019 at 5:03 PM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > On Wed, May 15, 2019 at 11:27:04AM -0700, Andy Lutomirski wrote: > > > Here's a very vague proposal that's kind of like what I've been > > > thinking over the past few days. The SGX inode could track, for each > > > page, a "safe-to-execute" bit. When you first open /dev/sgx/enclave, > > > you get a blank enclave and all pages are safe-to-execute. When you > > > do the ioctl to load context (which could be code, data, or anything > > > else), the kernel will check whether the *source* VMA is executable > > > and, if not, mark the page of the enclave being loaded as unsafe. > > > Once the enclave is initialized, the driver will clear the > > > safe-to-execute bit for any page that is successfully mapped writably. > > > > > > The intent is that a page of the enclave is safe-to-execute if that > > > page was populated from executable memory and not modified since then. > > > LSMs could then enforce a policy that you can map an enclave page RX > > > if the page is safe-to-execute, you can map any page you want for > > > write if there are no executable mappings, and you can only map a page > > > for write and execute simultaneously if you can EXECMOD permission. > > > This should allow an enclave to be loaded by userspace from a file > > > with EXECUTE rights. > > > > I'm still confused as to why you want to track execute permissions on the > > enclave pages and add SGX-specific LSM hooks. Is there anything that > > prevents userspace from building the enclave like any other DSO and then > > copying it into enclave memory? > > It's entirely possible that I'm the one missing something. But here's > why I think this: > > > I feel like I'm missing something. > > > > 1. Userspace loads enclave into regular memory, e.g. like a normal DSO. > > All mmap(), mprotect(), etc... calls are subject to all existing > > LSM policies. > > > > 2. Userspace opens /dev/sgx/enclave to instantiate a new enclave. > > > > 3. Userspace uses mmap() to allocate virtual memory for its enclave, > > again subject to all existing LSM policies (sane userspaces map it RO > > since the permissions eventually get tossed anyways). > > Is userspace actually requred to mmap() the enclave prior to EADDing things? It was a requirement prior to the API rework in v20, i.e. unless someone was really quick on the draw after the v20 update all existing userspace implementations mmap() the enclave before ECREATE. Requiring a valid enclave VMA for EADD shoudn't be too onerous. > > 4. SGX subsystem refuses to service page faults for enclaves that have > > not yet been initialized, e.g. signals SIGBUS or SIGSEGV. > > > > 5. Userspace invokes SGX ioctl() to copy enclave from regulary VMA to > > enclave VMA. > > > > 6. SGX ioctl() propagates VMA protection-related flags from source VMA > > to enclave VMA, e.g. invokes mprotect_fixup(). Enclave VMA(s) may > > be split as part of this process. > > Does this also call the LSM? If so, what is it expected to do? Nope. My reasoning behind skipping LSM checks is that the LSMs have already ok'd the source VMAs, similar to how dup_mmap() doesn't redo LSM checks. > What happens if there are different regions with different permissions on > the same page? SGX has 256-byte granularity right? No, EPC pages have 4k granularity. The EPC is divided into EPC pages. An EPC page is 4KB in size and always aligned on a 4KB boundary EEXTEND is the only aspect of SGX that works on 256-byte chunks, and that goofiness is primarily to keep the latency of EEXTEND low enough so that the instruction doesn't have to be interruptible, a la EINIT. > > > > 7. At all times, mprotect() calls on the enclave VMA are subject to > > existing LSM policies, i.e. it's not special cased for enclaves. > > I don't think the normal behavior actually works here. An enclave is > always MAP_SHARED, so (with SELinux) mprotecting() to X or RX requires > EXECUTE and mprotecting() to RWX requires extra permissions. Requiring extra permissions is good though, right? My thinking is to make the EADD "VMA copy" the happy/easy path, while using mprotect() to convert EPC memory to executable would require PROCESS__EXECMEM (assuming we back enclaves with anon inodes instead of /dev/sgx/enclave). > But user code can also mmap() the enclave again. What is supposed to > happen in that case? Hmm, it can't effectively re-mmap() the enclave as executable since entering the enclave requires using the correct virtual address range, i.e. EENTER would fail. It could, I think, do munmap()->mmap() to change the permissions. We could handle that case fairly easily by invoking security_file_mprotect() in SGX's mmap() hook if any pages have been added to the enclave, i.e. treat mmap() like mprotect().
On Thu, May 16, 2019 at 05:35:16PM -0700, Andy Lutomirski wrote: > On Thu, May 16, 2019 at 3:23 PM Xing, Cedric <cedric.xing@intel.com> wrote: > > And if you are with me on that bigger picture, the next question is: what > > should be the default behavior of security_sgx_mprot() for > > existing/non-SGX-aware LSM modules/policies? I'd say a reasonable default > > is to allow R, RW and RX, but not anything else. It'd suffice to get rid of > > EXECMEM/EXECMOD requirements on enclave applications. For SGX1, EPCM > > permissions are immutable so it really doesn't matter what > > security_sgx_mprot() does. For SGX2 and beyond, there's still time and new > > SGX-aware LSM modules/policies will probably have emerged by then. > > I hadn't thought about the SGX1 vs SGX2 difference. If the driver > initially only wants to support SGX1, then I guess we really could get > away with constraining the EPC flags based on the source page > permission and not restricting mprotect() and mmap() permissions on > /dev/sgx/enclave at all. No, SGX1 vs SGX2 support in the kernel is irrelevant. Well, unless the driver simply refuses to load on SGX2 hardware, but I don't think anyone wants to go that route. There is no enabling or attribute bit required to execute ENCLU[EMODPE], e.g. an enclave can effect RW->RWX in the EPCM on SGX2 hardware regardless of what the kernel is doing. IMO the kernel should ignore the EPCM from an LSM perspective.
On 5/17/19 11:09 AM, Sean Christopherson wrote: > On Fri, May 17, 2019 at 09:53:06AM -0400, Stephen Smalley wrote: >> On 5/16/19 6:23 PM, Xing, Cedric wrote: >>> I thought EXECMOD applied to files (and memory mappings backed by them) but >>> I was probably wrong. It sounds like EXECMOD applies to the whole process so >>> would allow all pages within a process's address space to be modified then >>> executed, regardless the backing files. Am I correct this time? >> >> No, you were correct the first time I think; EXECMOD is used to control >> whether a process can make executable a private file mapping that has >> previously been modified (e.g. text relocation); it is a special case to >> support text relocations without having to allow full EXECMEM (i.e. execute >> arbitrary memory). >> >> SELinux checks relevant to W^X include: >> >> - EXECMEM: mmap/mprotect PROT_EXEC an anonymous mapping (regardless of >> PROT_WRITE, since we know the content has to have been written at some >> point) or a private file mapping that is also PROT_WRITE. >> - EXECMOD: mprotect PROT_EXEC a private file mapping that has been >> previously modified, typically for text relocations, >> - FILE__WRITE: mmap/mprotect PROT_WRITE a shared file mapping, >> - FILE__EXECUTE: mmap/mprotect PROT_EXEC a file mapping. >> >> (ignoring EXECSTACK and EXECHEAP here since they aren't really relevant to >> this discussion) >> >> So if you want to ensure W^X, then you wouldn't allow EXECMEM for the >> process, EXECMOD by the process to any file, and the combination of both >> FILE__WRITE and FILE__EXECUTE by the process to any file. >> >> If the /dev/sgx/enclave mappings are MAP_SHARED and you aren't using an >> anonymous inode, then I would expect that only the FILE__WRITE and >> FILE__EXECUTE checks are relevant. > > Yep, I was just typing this up in a different thread: > > I think we may want to change the SGX API to alloc an anon inode for each > enclave instead of hanging every enclave off of the /dev/sgx/enclave inode. > Because /dev/sgx/enclave is NOT private, SELinux's file_map_prot_check() > will only require FILE__WRITE and FILE__EXECUTE to mprotect() enclave VMAs > to RWX. Backing each enclave with an anon inode will make SELinux treat > EPC memory like anonymous mappings, which is what we want (I think), e.g. > making *any* EPC page executable will require PROCESS__EXECMEM (SGX is > 64-bit only at this point, so SELinux will always have default_noexec). I don't think we want to require EXECMEM (or equivalently both FILE__WRITE and FILE__EXECUTE to /dev/sgx/enclave) for making any EPC page executable, only if the page is also writable or previously modified. The intent is to prevent arbitrary code execution without EXECMEM (or FILE__WRITE|FILE__EXECUTE), while still allowing enclaves to be created without EXECMEM as long as the EPC page mapping is only ever mapped RX and its initial contents came from an unmodified file mapping that was PROT_EXEC (and hence already checked via FILE__EXECUTE).
> On May 17, 2019, at 9:20 AM, Stephen Smalley <sds@tycho.nsa.gov> wrote: > >> On 5/17/19 11:09 AM, Sean Christopherson wrote: >>> On Fri, May 17, 2019 at 09:53:06AM -0400, Stephen Smalley wrote: >>>> On 5/16/19 6:23 PM, Xing, Cedric wrote: >>>> I thought EXECMOD applied to files (and memory mappings backed by them) but >>>> I was probably wrong. It sounds like EXECMOD applies to the whole process so >>>> would allow all pages within a process's address space to be modified then >>>> executed, regardless the backing files. Am I correct this time? >>> >>> No, you were correct the first time I think; EXECMOD is used to control >>> whether a process can make executable a private file mapping that has >>> previously been modified (e.g. text relocation); it is a special case to >>> support text relocations without having to allow full EXECMEM (i.e. execute >>> arbitrary memory). >>> >>> SELinux checks relevant to W^X include: >>> >>> - EXECMEM: mmap/mprotect PROT_EXEC an anonymous mapping (regardless of >>> PROT_WRITE, since we know the content has to have been written at some >>> point) or a private file mapping that is also PROT_WRITE. >>> - EXECMOD: mprotect PROT_EXEC a private file mapping that has been >>> previously modified, typically for text relocations, >>> - FILE__WRITE: mmap/mprotect PROT_WRITE a shared file mapping, >>> - FILE__EXECUTE: mmap/mprotect PROT_EXEC a file mapping. >>> >>> (ignoring EXECSTACK and EXECHEAP here since they aren't really relevant to >>> this discussion) >>> >>> So if you want to ensure W^X, then you wouldn't allow EXECMEM for the >>> process, EXECMOD by the process to any file, and the combination of both >>> FILE__WRITE and FILE__EXECUTE by the process to any file. >>> >>> If the /dev/sgx/enclave mappings are MAP_SHARED and you aren't using an >>> anonymous inode, then I would expect that only the FILE__WRITE and >>> FILE__EXECUTE checks are relevant. >> Yep, I was just typing this up in a different thread: >> I think we may want to change the SGX API to alloc an anon inode for each >> enclave instead of hanging every enclave off of the /dev/sgx/enclave inode. >> Because /dev/sgx/enclave is NOT private, SELinux's file_map_prot_check() >> will only require FILE__WRITE and FILE__EXECUTE to mprotect() enclave VMAs >> to RWX. Backing each enclave with an anon inode will make SELinux treat >> EPC memory like anonymous mappings, which is what we want (I think), e.g. >> making *any* EPC page executable will require PROCESS__EXECMEM (SGX is >> 64-bit only at this point, so SELinux will always have default_noexec). > > I don't think we want to require EXECMEM (or equivalently both FILE__WRITE and FILE__EXECUTE to /dev/sgx/enclave) for making any EPC page executable, only if the page is also writable or previously modified. The intent is to prevent arbitrary code execution without EXECMEM (or FILE__WRITE|FILE__EXECUTE), while still allowing enclaves to be created without EXECMEM as long as the EPC page mapping is only ever mapped RX and its initial contents came from an unmodified file mapping that was PROT_EXEC (and hence already checked via FILE__EXECUTE). That agrees with my thoughts. Actually plumbing everything together so this works could be a bit interesting. I assume it’ll need a special case in SELinux or maybe a new vm_op.
On 5/17/19 12:20 PM, Stephen Smalley wrote: > On 5/17/19 11:09 AM, Sean Christopherson wrote: >> On Fri, May 17, 2019 at 09:53:06AM -0400, Stephen Smalley wrote: >>> On 5/16/19 6:23 PM, Xing, Cedric wrote: >>>> I thought EXECMOD applied to files (and memory mappings backed by >>>> them) but >>>> I was probably wrong. It sounds like EXECMOD applies to the whole >>>> process so >>>> would allow all pages within a process's address space to be >>>> modified then >>>> executed, regardless the backing files. Am I correct this time? >>> >>> No, you were correct the first time I think; EXECMOD is used to control >>> whether a process can make executable a private file mapping that has >>> previously been modified (e.g. text relocation); it is a special case to >>> support text relocations without having to allow full EXECMEM (i.e. >>> execute >>> arbitrary memory). >>> >>> SELinux checks relevant to W^X include: >>> >>> - EXECMEM: mmap/mprotect PROT_EXEC an anonymous mapping (regardless of >>> PROT_WRITE, since we know the content has to have been written at some >>> point) or a private file mapping that is also PROT_WRITE. >>> - EXECMOD: mprotect PROT_EXEC a private file mapping that has been >>> previously modified, typically for text relocations, >>> - FILE__WRITE: mmap/mprotect PROT_WRITE a shared file mapping, >>> - FILE__EXECUTE: mmap/mprotect PROT_EXEC a file mapping. >>> >>> (ignoring EXECSTACK and EXECHEAP here since they aren't really >>> relevant to >>> this discussion) >>> >>> So if you want to ensure W^X, then you wouldn't allow EXECMEM for the >>> process, EXECMOD by the process to any file, and the combination of both >>> FILE__WRITE and FILE__EXECUTE by the process to any file. >>> >>> If the /dev/sgx/enclave mappings are MAP_SHARED and you aren't using an >>> anonymous inode, then I would expect that only the FILE__WRITE and >>> FILE__EXECUTE checks are relevant. >> >> Yep, I was just typing this up in a different thread: >> >> I think we may want to change the SGX API to alloc an anon inode for each >> enclave instead of hanging every enclave off of the /dev/sgx/enclave >> inode. >> Because /dev/sgx/enclave is NOT private, SELinux's file_map_prot_check() >> will only require FILE__WRITE and FILE__EXECUTE to mprotect() enclave >> VMAs >> to RWX. Backing each enclave with an anon inode will make SELinux treat >> EPC memory like anonymous mappings, which is what we want (I think), e.g. >> making *any* EPC page executable will require PROCESS__EXECMEM (SGX is >> 64-bit only at this point, so SELinux will always have default_noexec). > > I don't think we want to require EXECMEM (or equivalently both > FILE__WRITE and FILE__EXECUTE to /dev/sgx/enclave) for making any EPC > page executable, only if the page is also writable or previously > modified. The intent is to prevent arbitrary code execution without > EXECMEM (or FILE__WRITE|FILE__EXECUTE), while still allowing enclaves to > be created without EXECMEM as long as the EPC page mapping is only ever > mapped RX and its initial contents came from an unmodified file mapping > that was PROT_EXEC (and hence already checked via FILE__EXECUTE). Also, just to be clear, there is nothing inherently better about checking EXECMEM instead of checking both FILE__WRITE and FILE__EXECUTE to the /dev/sgx/enclave inode, so I wouldn't switch to using anon inodes for that reason. Using anon inodes also unfortunately disables SELinux inode-based checking since we no longer have any useful inode information, so you'd lose out on SELinux ioctl whitelisting on those enclave inodes if that matters.
> On May 17, 2019, at 9:37 AM, Stephen Smalley <sds@tycho.nsa.gov> wrote: > >> On 5/17/19 12:20 PM, Stephen Smalley wrote: >>> On 5/17/19 11:09 AM, Sean Christopherson wrote: >>>> On Fri, May 17, 2019 at 09:53:06AM -0400, Stephen Smalley wrote: >>>>> On 5/16/19 6:23 PM, Xing, Cedric wrote: >>>>> I thought EXECMOD applied to files (and memory mappings backed by them) but >>>>> I was probably wrong. It sounds like EXECMOD applies to the whole process so >>>>> would allow all pages within a process's address space to be modified then >>>>> executed, regardless the backing files. Am I correct this time? >>>> >>>> No, you were correct the first time I think; EXECMOD is used to control >>>> whether a process can make executable a private file mapping that has >>>> previously been modified (e.g. text relocation); it is a special case to >>>> support text relocations without having to allow full EXECMEM (i.e. execute >>>> arbitrary memory). >>>> >>>> SELinux checks relevant to W^X include: >>>> >>>> - EXECMEM: mmap/mprotect PROT_EXEC an anonymous mapping (regardless of >>>> PROT_WRITE, since we know the content has to have been written at some >>>> point) or a private file mapping that is also PROT_WRITE. >>>> - EXECMOD: mprotect PROT_EXEC a private file mapping that has been >>>> previously modified, typically for text relocations, >>>> - FILE__WRITE: mmap/mprotect PROT_WRITE a shared file mapping, >>>> - FILE__EXECUTE: mmap/mprotect PROT_EXEC a file mapping. >>>> >>>> (ignoring EXECSTACK and EXECHEAP here since they aren't really relevant to >>>> this discussion) >>>> >>>> So if you want to ensure W^X, then you wouldn't allow EXECMEM for the >>>> process, EXECMOD by the process to any file, and the combination of both >>>> FILE__WRITE and FILE__EXECUTE by the process to any file. >>>> >>>> If the /dev/sgx/enclave mappings are MAP_SHARED and you aren't using an >>>> anonymous inode, then I would expect that only the FILE__WRITE and >>>> FILE__EXECUTE checks are relevant. >>> >>> Yep, I was just typing this up in a different thread: >>> >>> I think we may want to change the SGX API to alloc an anon inode for each >>> enclave instead of hanging every enclave off of the /dev/sgx/enclave inode. >>> Because /dev/sgx/enclave is NOT private, SELinux's file_map_prot_check() >>> will only require FILE__WRITE and FILE__EXECUTE to mprotect() enclave VMAs >>> to RWX. Backing each enclave with an anon inode will make SELinux treat >>> EPC memory like anonymous mappings, which is what we want (I think), e.g. >>> making *any* EPC page executable will require PROCESS__EXECMEM (SGX is >>> 64-bit only at this point, so SELinux will always have default_noexec). >> I don't think we want to require EXECMEM (or equivalently both FILE__WRITE and FILE__EXECUTE to /dev/sgx/enclave) for making any EPC page executable, only if the page is also writable or previously modified. The intent is to prevent arbitrary code execution without EXECMEM (or FILE__WRITE|FILE__EXECUTE), while still allowing enclaves to be created without EXECMEM as long as the EPC page mapping is only ever mapped RX and its initial contents came from an unmodified file mapping that was PROT_EXEC (and hence already checked via FILE__EXECUTE). > > Also, just to be clear, there is nothing inherently better about checking EXECMEM instead of checking both FILE__WRITE and FILE__EXECUTE to the /dev/sgx/enclave inode, so I wouldn't switch to using anon inodes for that reason. Using anon inodes also unfortunately disables SELinux inode-based checking since we no longer have any useful inode information, so you'd lose out on SELinux ioctl whitelisting on those enclave inodes if that matters. How can that work? Unless the API changes fairly radically, users fundamentally need to both write and execute the enclave. Some of it will be written only from already executable pages, and some privilege should be needed to execute any enclave page that was not loaded like this.
On Fri, May 17, 2019 at 12:37:40PM -0400, Stephen Smalley wrote: > On 5/17/19 12:20 PM, Stephen Smalley wrote: > >On 5/17/19 11:09 AM, Sean Christopherson wrote: > >>I think we may want to change the SGX API to alloc an anon inode for each > >>enclave instead of hanging every enclave off of the /dev/sgx/enclave > >>inode. > >>Because /dev/sgx/enclave is NOT private, SELinux's file_map_prot_check() > >>will only require FILE__WRITE and FILE__EXECUTE to mprotect() enclave > >>VMAs > >>to RWX. Backing each enclave with an anon inode will make SELinux treat > >>EPC memory like anonymous mappings, which is what we want (I think), e.g. > >>making *any* EPC page executable will require PROCESS__EXECMEM (SGX is > >>64-bit only at this point, so SELinux will always have default_noexec). > > > >I don't think we want to require EXECMEM (or equivalently both FILE__WRITE > >and FILE__EXECUTE to /dev/sgx/enclave) for making any EPC page executable, > >only if the page is also writable or previously modified. The intent is > >to prevent arbitrary code execution without EXECMEM (or > >FILE__WRITE|FILE__EXECUTE), while still allowing enclaves to be created > >without EXECMEM as long as the EPC page mapping is only ever mapped RX and > >its initial contents came from an unmodified file mapping that was > >PROT_EXEC (and hence already checked via FILE__EXECUTE). The idea is that by providing an SGX ioctl() to propagate VMA permissions from a source VMA, EXECMEM wouldn't be required to make an EPC page executable. E.g. userspace establishes an enclave in non-EPC memory from an unmodified file (with FILE__EXECUTE perms), and the uses the SGX ioctl() to copy the contents and permissions into EPC memory. > Also, just to be clear, there is nothing inherently better about checking > EXECMEM instead of checking both FILE__WRITE and FILE__EXECUTE to the > /dev/sgx/enclave inode, so I wouldn't switch to using anon inodes for that > reason. Using anon inodes also unfortunately disables SELinux inode-based > checking since we no longer have any useful inode information, so you'd lose > out on SELinux ioctl whitelisting on those enclave inodes if that matters. The problem is that all enclaves are associated with a single inode, i.e. /dev/sgx/enclave. /dev/sgx/enclave is a char device whose purpose is to provide ioctls() and to allow mmap()'ing EPC memory. In no way is it associated with the content that actually gets loaded into EPC memory. The actual file that contains the enclave's contents (assuming the enclave came from a file) is a separate regular file that the SGX subsystem never sees. AIUI, having FILE__WRITE and FILE__EXECUTE on /dev/sgx/enclave would allow *any* enclave/process to map EPC as RWX. Moving to anon inodes and thus PROCESS__EXECMEM achieves per-process granularity.
On 5/17/19 1:29 PM, Sean Christopherson wrote: > On Fri, May 17, 2019 at 12:37:40PM -0400, Stephen Smalley wrote: >> On 5/17/19 12:20 PM, Stephen Smalley wrote: >>> On 5/17/19 11:09 AM, Sean Christopherson wrote: >>>> I think we may want to change the SGX API to alloc an anon inode for each >>>> enclave instead of hanging every enclave off of the /dev/sgx/enclave >>>> inode. >>>> Because /dev/sgx/enclave is NOT private, SELinux's file_map_prot_check() >>>> will only require FILE__WRITE and FILE__EXECUTE to mprotect() enclave >>>> VMAs >>>> to RWX. Backing each enclave with an anon inode will make SELinux treat >>>> EPC memory like anonymous mappings, which is what we want (I think), e.g. >>>> making *any* EPC page executable will require PROCESS__EXECMEM (SGX is >>>> 64-bit only at this point, so SELinux will always have default_noexec). >>> >>> I don't think we want to require EXECMEM (or equivalently both FILE__WRITE >>> and FILE__EXECUTE to /dev/sgx/enclave) for making any EPC page executable, >>> only if the page is also writable or previously modified. The intent is >>> to prevent arbitrary code execution without EXECMEM (or >>> FILE__WRITE|FILE__EXECUTE), while still allowing enclaves to be created >>> without EXECMEM as long as the EPC page mapping is only ever mapped RX and >>> its initial contents came from an unmodified file mapping that was >>> PROT_EXEC (and hence already checked via FILE__EXECUTE). > > The idea is that by providing an SGX ioctl() to propagate VMA permissions > from a source VMA, EXECMEM wouldn't be required to make an EPC page > executable. E.g. userspace establishes an enclave in non-EPC memory from > an unmodified file (with FILE__EXECUTE perms), and the uses the SGX ioctl() > to copy the contents and permissions into EPC memory. > >> Also, just to be clear, there is nothing inherently better about checking >> EXECMEM instead of checking both FILE__WRITE and FILE__EXECUTE to the >> /dev/sgx/enclave inode, so I wouldn't switch to using anon inodes for that >> reason. Using anon inodes also unfortunately disables SELinux inode-based >> checking since we no longer have any useful inode information, so you'd lose >> out on SELinux ioctl whitelisting on those enclave inodes if that matters. > > The problem is that all enclaves are associated with a single inode, i.e. > /dev/sgx/enclave. /dev/sgx/enclave is a char device whose purpose is to > provide ioctls() and to allow mmap()'ing EPC memory. In no way is it > associated with the content that actually gets loaded into EPC memory. > > The actual file that contains the enclave's contents (assuming the enclave > came from a file) is a separate regular file that the SGX subsystem never > sees. > > AIUI, having FILE__WRITE and FILE__EXECUTE on /dev/sgx/enclave would allow > *any* enclave/process to map EPC as RWX. Moving to anon inodes and thus > PROCESS__EXECMEM achieves per-process granularity. > No, FILE__WRITE and FILE__EXECUTE are a check between a process and a file, so you can ensure that only whitelisted processes are allowed both to /dev/sgx/enclave.
> On May 17, 2019, at 10:29 AM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > >> On Fri, May 17, 2019 at 12:37:40PM -0400, Stephen Smalley wrote: >>> On 5/17/19 12:20 PM, Stephen Smalley wrote: >>>> On 5/17/19 11:09 AM, Sean Christopherson wrote: >>>> I think we may want to change the SGX API to alloc an anon inode for each >>>> enclave instead of hanging every enclave off of the /dev/sgx/enclave >>>> inode. >>>> Because /dev/sgx/enclave is NOT private, SELinux's file_map_prot_check() >>>> will only require FILE__WRITE and FILE__EXECUTE to mprotect() enclave >>>> VMAs >>>> to RWX. Backing each enclave with an anon inode will make SELinux treat >>>> EPC memory like anonymous mappings, which is what we want (I think), e.g. >>>> making *any* EPC page executable will require PROCESS__EXECMEM (SGX is >>>> 64-bit only at this point, so SELinux will always have default_noexec). >>> >>> I don't think we want to require EXECMEM (or equivalently both FILE__WRITE >>> and FILE__EXECUTE to /dev/sgx/enclave) for making any EPC page executable, >>> only if the page is also writable or previously modified. The intent is >>> to prevent arbitrary code execution without EXECMEM (or >>> FILE__WRITE|FILE__EXECUTE), while still allowing enclaves to be created >>> without EXECMEM as long as the EPC page mapping is only ever mapped RX and >>> its initial contents came from an unmodified file mapping that was >>> PROT_EXEC (and hence already checked via FILE__EXECUTE). > > The idea is that by providing an SGX ioctl() to propagate VMA permissions > from a source VMA, EXECMEM wouldn't be required to make an EPC page > executable. E.g. userspace establishes an enclave in non-EPC memory from > an unmodified file (with FILE__EXECUTE perms), and the uses the SGX ioctl() > to copy the contents and permissions into EPC memory. > >> Also, just to be clear, there is nothing inherently better about checking >> EXECMEM instead of checking both FILE__WRITE and FILE__EXECUTE to the >> /dev/sgx/enclave inode, so I wouldn't switch to using anon inodes for that >> reason. Using anon inodes also unfortunately disables SELinux inode-based >> checking since we no longer have any useful inode information, so you'd lose >> out on SELinux ioctl whitelisting on those enclave inodes if that matters. > > The problem is that all enclaves are associated with a single inode, i.e. > /dev/sgx/enclave. /dev/sgx/enclave is a char device whose purpose is to > provide ioctls() and to allow mmap()'ing EPC memory. In no way is it > associated with the content that actually gets loaded into EPC memory. > > The actual file that contains the enclave's contents (assuming the enclave > came from a file) is a separate regular file that the SGX subsystem never > sees. > > AIUI, having FILE__WRITE and FILE__EXECUTE on /dev/sgx/enclave would allow > *any* enclave/process to map EPC as RWX. Moving to anon inodes and thus > PROCESS__EXECMEM achieves per-process granularity. How does anon_inode make any difference? Anon_inode is not the same thing as anon_vma.
On Fri, May 17, 2019 at 01:42:50PM -0400, Stephen Smalley wrote: > On 5/17/19 1:29 PM, Sean Christopherson wrote: > >AIUI, having FILE__WRITE and FILE__EXECUTE on /dev/sgx/enclave would allow > >*any* enclave/process to map EPC as RWX. Moving to anon inodes and thus > >PROCESS__EXECMEM achieves per-process granularity. > > > > No, FILE__WRITE and FILE__EXECUTE are a check between a process and a file, > so you can ensure that only whitelisted processes are allowed both to > /dev/sgx/enclave. Ah, so each process has its own FILE__* permissions for a specific set of files? Does that allow differentiating between a process making an EPC page RWX and a process making two separate EPC pages RW and RX?
On Fri, May 17, 2019 at 10:43:01AM -0700, Andy Lutomirski wrote: > > > On May 17, 2019, at 10:29 AM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > > > AIUI, having FILE__WRITE and FILE__EXECUTE on /dev/sgx/enclave would allow > > *any* enclave/process to map EPC as RWX. Moving to anon inodes and thus > > PROCESS__EXECMEM achieves per-process granularity. > > How does anon_inode make any difference? Anon_inode is not the same thing as > anon_vma. In this snippet, IS_PRIVATE() is true for anon inodes, false for /dev/sgx/enclave. Because EPC memory is always shared, SELinux will never check PROCESS__EXECMEM for mprotect() on/dev/sgx/enclave. static int file_map_prot_check(struct file *file, unsigned long prot, int shared) { const struct cred *cred = current_cred(); u32 sid = cred_sid(cred); int rc = 0; if (default_noexec && (prot & PROT_EXEC) && (!file || IS_PRIVATE(file_inode(file)) || (!shared && (prot & PROT_WRITE)))) { /* * We are making executable an anonymous mapping or a * private file mapping that will also be writable. * This has an additional check. */ rc = avc_has_perm(&selinux_state, sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM, NULL); if (rc) goto error; } ... }
On Fri, May 17, 2019 at 10:55 AM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > In this snippet, IS_PRIVATE() is true for anon inodes, false for > /dev/sgx/enclave. Because EPC memory is always shared, SELinux will never > check PROCESS__EXECMEM for mprotect() on/dev/sgx/enclave. Why _does_ the memory have to be shared? Shared mmap() is fundamentally less secure than private mmap, since by definition it means "oh, somebody else has access to it too and might modify it under us". Why does the SGX logic care about things like that? Normal executables are just private mappings of an underlying file, I'm not sure why the SGX interface has to have that shared thing, and why the interface has to have a device node in the first place when you have system calls for setup anyway. So why don't the system calls just work on perfectly normal anonymous mmap's? Why a device node, and why must it be shared to begin with? Linus
On 5/17/19 1:12 PM, Andy Lutomirski wrote: > > >> On May 17, 2019, at 9:37 AM, Stephen Smalley <sds@tycho.nsa.gov> wrote: >> >>> On 5/17/19 12:20 PM, Stephen Smalley wrote: >>>> On 5/17/19 11:09 AM, Sean Christopherson wrote: >>>>> On Fri, May 17, 2019 at 09:53:06AM -0400, Stephen Smalley wrote: >>>>>> On 5/16/19 6:23 PM, Xing, Cedric wrote: >>>>>> I thought EXECMOD applied to files (and memory mappings backed by them) but >>>>>> I was probably wrong. It sounds like EXECMOD applies to the whole process so >>>>>> would allow all pages within a process's address space to be modified then >>>>>> executed, regardless the backing files. Am I correct this time? >>>>> >>>>> No, you were correct the first time I think; EXECMOD is used to control >>>>> whether a process can make executable a private file mapping that has >>>>> previously been modified (e.g. text relocation); it is a special case to >>>>> support text relocations without having to allow full EXECMEM (i.e. execute >>>>> arbitrary memory). >>>>> >>>>> SELinux checks relevant to W^X include: >>>>> >>>>> - EXECMEM: mmap/mprotect PROT_EXEC an anonymous mapping (regardless of >>>>> PROT_WRITE, since we know the content has to have been written at some >>>>> point) or a private file mapping that is also PROT_WRITE. >>>>> - EXECMOD: mprotect PROT_EXEC a private file mapping that has been >>>>> previously modified, typically for text relocations, >>>>> - FILE__WRITE: mmap/mprotect PROT_WRITE a shared file mapping, >>>>> - FILE__EXECUTE: mmap/mprotect PROT_EXEC a file mapping. >>>>> >>>>> (ignoring EXECSTACK and EXECHEAP here since they aren't really relevant to >>>>> this discussion) >>>>> >>>>> So if you want to ensure W^X, then you wouldn't allow EXECMEM for the >>>>> process, EXECMOD by the process to any file, and the combination of both >>>>> FILE__WRITE and FILE__EXECUTE by the process to any file. >>>>> >>>>> If the /dev/sgx/enclave mappings are MAP_SHARED and you aren't using an >>>>> anonymous inode, then I would expect that only the FILE__WRITE and >>>>> FILE__EXECUTE checks are relevant. >>>> >>>> Yep, I was just typing this up in a different thread: >>>> >>>> I think we may want to change the SGX API to alloc an anon inode for each >>>> enclave instead of hanging every enclave off of the /dev/sgx/enclave inode. >>>> Because /dev/sgx/enclave is NOT private, SELinux's file_map_prot_check() >>>> will only require FILE__WRITE and FILE__EXECUTE to mprotect() enclave VMAs >>>> to RWX. Backing each enclave with an anon inode will make SELinux treat >>>> EPC memory like anonymous mappings, which is what we want (I think), e.g. >>>> making *any* EPC page executable will require PROCESS__EXECMEM (SGX is >>>> 64-bit only at this point, so SELinux will always have default_noexec). >>> I don't think we want to require EXECMEM (or equivalently both FILE__WRITE and FILE__EXECUTE to /dev/sgx/enclave) for making any EPC page executable, only if the page is also writable or previously modified. The intent is to prevent arbitrary code execution without EXECMEM (or FILE__WRITE|FILE__EXECUTE), while still allowing enclaves to be created without EXECMEM as long as the EPC page mapping is only ever mapped RX and its initial contents came from an unmodified file mapping that was PROT_EXEC (and hence already checked via FILE__EXECUTE). >> >> Also, just to be clear, there is nothing inherently better about checking EXECMEM instead of checking both FILE__WRITE and FILE__EXECUTE to the /dev/sgx/enclave inode, so I wouldn't switch to using anon inodes for that reason. Using anon inodes also unfortunately disables SELinux inode-based checking since we no longer have any useful inode information, so you'd lose out on SELinux ioctl whitelisting on those enclave inodes if that matters. > > How can that work? Unless the API changes fairly radically, users fundamentally need to both write and execute the enclave. Some of it will be written only from already executable pages, and some privilege should be needed to execute any enclave page that was not loaded like this. I'm not sure what the API is. Let's say they do something like this: fd = open("/dev/sgx/enclave", O_RDONLY); addr = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0); stuff addr into ioctl args ioctl(fd, ENCLAVE_CREATE, &ioctlargs); ioctl(fd, ENCLAVE_ADD_PAGE, &ioctlargs); ioctl(fd, ENCLAVE_INIT, &ioctlargs); The important points are that they do not open /dev/sgx/enclave with write access (otherwise they will trigger FILE__WRITE at open time, and later encounter FILE__EXECUTE as well during mmap, thereby requiring both to be allowed to /dev/sgx/enclave), and that they do not request PROT_WRITE to the resulting mapping (otherwise they will trigger FILE__WRITE at mmap time). Then only FILE__READ and FILE__EXECUTE are required to /dev/sgx/enclave in policy. If they switch to an anon inode, then any mmap PROT_EXEC of the opened file will trigger an EXECMEM check, at least as currently implemented, as we have no useful backing inode information.
On 5/17/19 1:50 PM, Sean Christopherson wrote: > On Fri, May 17, 2019 at 01:42:50PM -0400, Stephen Smalley wrote: >> On 5/17/19 1:29 PM, Sean Christopherson wrote: >>> AIUI, having FILE__WRITE and FILE__EXECUTE on /dev/sgx/enclave would allow >>> *any* enclave/process to map EPC as RWX. Moving to anon inodes and thus >>> PROCESS__EXECMEM achieves per-process granularity. >>> >> >> No, FILE__WRITE and FILE__EXECUTE are a check between a process and a file, >> so you can ensure that only whitelisted processes are allowed both to >> /dev/sgx/enclave. > > Ah, so each process has its own FILE__* permissions for a specific set of > files? That's correct. > Does that allow differentiating between a process making an EPC page RWX > and a process making two separate EPC pages RW and RX? Not if they are backed by the same inode, nor if they are all backed by anon inodes, at least not as currently implemented.
On Fri, May 17, 2019 at 11:04:22AM -0700, Linus Torvalds wrote: > On Fri, May 17, 2019 at 10:55 AM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > In this snippet, IS_PRIVATE() is true for anon inodes, false for > > /dev/sgx/enclave. Because EPC memory is always shared, SELinux will never > > check PROCESS__EXECMEM for mprotect() on/dev/sgx/enclave. > > Why _does_ the memory have to be shared? Shared mmap() is > fundamentally less secure than private mmap, since by definition it > means "oh, somebody else has access to it too and might modify it > under us". > > Why does the SGX logic care about things like that? Normal executables > are just private mappings of an underlying file, I'm not sure why the > SGX interface has to have that shared thing, and why the interface has > to have a device node in the first place when you have system calls > for setup anyway. > > So why don't the system calls just work on perfectly normal anonymous > mmap's? Why a device node, and why must it be shared to begin with? I agree that conceptually EPC is private memory, but because EPC is managed as a separate memory pool, SGX tags it VM_PFNMAP and manually inserts PFNs, i.e. EPC effectively it gets classified as IO memory. And vmf_insert_pfn_prot() doesn't like writable private IO mappings: BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags));
On Fri, May 17, 2019 at 11:21 AM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > I agree that conceptually EPC is private memory, but because EPC is > managed as a separate memory pool, SGX tags it VM_PFNMAP and manually > inserts PFNs, i.e. EPC effectively it gets classified as IO memory. > > And vmf_insert_pfn_prot() doesn't like writable private IO mappings: > > BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags)); Hmm. I haven't looked into why you want to do your own page insertion and not just "use existing pages", but I'm sure there's some reason. It looks like the "shared vs private" inode part is a red herring, though. You might as well give each opener of the sgx node its own inode - and you probably should. Then you can keep track of the pages that have been added in the inode->i_mapping, and you could avoid the whole PFN thing entirely. I still am not a huge fan of the device node in the first place, but I guess it's just one more place where a system admin can then give (or deny) access to a kernel feature from users. I guess the kvm people do the same thing, for not necessarily any better reasons. With the PFNMAP model I guess the SGX memory ends up being unswappable - at least done the obvious way. Again, the way I'd expect it to be done is as a shmem inode - that would I think be a better model. But I think that's a largely internal design decision, and the device node could just do that eventually (and the mmap could just map the populated shmem information into memory, no PFNMAP needed - the inode and the mapping could be "read-only" as far as the _user_ is concerned, but the i_mapping then gets populated by the ioctl's). I have not actually looked at any of the SGX patches, so maybe you're already doing something like that (although the PFNMAP comment makes me think not), and quite possibly there's some fundamental reason why you can't just use the shmem approach. So my high-level reaction here may be just the rantings of somebody who just isn't familiar with what you do. My "why not shmem and regular mmap" questions come from a 30000ft view without knowing any of the details. Linus
On Fri, May 17, 2019 at 11:33:30AM -0700, Linus Torvalds wrote: > On Fri, May 17, 2019 at 11:21 AM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > I agree that conceptually EPC is private memory, but because EPC is > > managed as a separate memory pool, SGX tags it VM_PFNMAP and manually > > inserts PFNs, i.e. EPC effectively it gets classified as IO memory. > > > > And vmf_insert_pfn_prot() doesn't like writable private IO mappings: > > > > BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags)); > > Hmm. I haven't looked into why you want to do your own page insertion > and not just "use existing pages", but I'm sure there's some reason. Outside of the SGX subsystem, the kernel is unaware of EPC memory, e.g. BIOS enumerates it as reserved memory in the e820 tables, or not at all. On current hardware, EPC is backed by system memory, but it's protected by a range registers (and other stuff) and can't be accessed directly except when the CPU is in "enclave mode", i.e. executing an enclave in CPL3. To execute an enclave it must first be built, and because EPC memory can't be written outside of enclave mode, the only way to build the enclave is via dedicated CPL0 ISA, e.g. ENCLS[EADD]. > It looks like the "shared vs private" inode part is a red herring, > though. You might as well give each opener of the sgx node its own > inode - and you probably should. Then you can keep track of the pages > that have been added in the inode->i_mapping, and you could avoid the > whole PFN thing entirely. I still am not a huge fan of the device node > in the first place, but I guess it's just one more place where a > system admin can then give (or deny) access to a kernel feature from > users. I guess the kvm people do the same thing, for not necessarily > any better reasons. > > With the PFNMAP model I guess the SGX memory ends up being unswappable > - at least done the obvious way. EPC memory is swappable in it's own terms, e.g. pages can be swapped from EPC to system RAM and vice versa, but again moving pages in and out of the EPC can only be done through dedicated CPL0 ISA. And there are additional TLB flushing requirements, evicted pages need to be refcounted against the enclave, evicted pages need an anchor in the EPC to ensure freshness, etc... Long story short, we decided to manage EPC in the SGX subsystem as a separate memory pool rather than modify the kernel's MMU to teach it how to deal with EPC. > Again, the way I'd expect it to be done is as a shmem inode - that > would I think be a better model. But I think that's a largely internal > design decision, and the device node could just do that eventually > (and the mmap could just map the populated shmem information into > memory, no PFNMAP needed - the inode and the mapping could be > "read-only" as far as the _user_ is concerned, but the i_mapping then > gets populated by the ioctl's). > > I have not actually looked at any of the SGX patches, so maybe you're > already doing something like that (although the PFNMAP comment makes > me think not), and quite possibly there's some fundamental reason why > you can't just use the shmem approach. > > So my high-level reaction here may be just the rantings of somebody > who just isn't familiar with what you do. My "why not shmem and > regular mmap" questions come from a 30000ft view without knowing any > of the details. > > Linus
> On May 17, 2019, at 11:21 AM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > >> On Fri, May 17, 2019 at 11:04:22AM -0700, Linus Torvalds wrote: >> On Fri, May 17, 2019 at 10:55 AM Sean Christopherson >> <sean.j.christopherson@intel.com> wrote: >>> >>> In this snippet, IS_PRIVATE() is true for anon inodes, false for >>> /dev/sgx/enclave. Because EPC memory is always shared, SELinux will never >>> check PROCESS__EXECMEM for mprotect() on/dev/sgx/enclave. >> >> Why _does_ the memory have to be shared? Shared mmap() is >> fundamentally less secure than private mmap, since by definition it >> means "oh, somebody else has access to it too and might modify it >> under us". >> >> Why does the SGX logic care about things like that? Normal executables >> are just private mappings of an underlying file, I'm not sure why the >> SGX interface has to have that shared thing, and why the interface has >> to have a device node in the first place when you have system calls >> for setup anyway. >> >> So why don't the system calls just work on perfectly normal anonymous >> mmap's? Why a device node, and why must it be shared to begin with? > > I agree that conceptually EPC is private memory, but because EPC is > managed as a separate memory pool, SGX tags it VM_PFNMAP and manually > inserts PFNs, i.e. EPC effectively it gets classified as IO memory. > > And vmf_insert_pfn_prot() doesn't like writable private IO mappings: > > BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags)); I don’t see how it could be anonymous even in principle. The kernel can’t *read* the memory — how could we possibly CoW it? And we can’t share an RO backing pages between two different enclaves because the CPU won’t let us — each EPC page belongs to a particular enclave. And fork()ing an enclave is right out. So I agree that MAP_ANONYMOUS would be nice conceptually, but I don’t see how it would work.
On 5/17/19 2:05 PM, Stephen Smalley wrote: > On 5/17/19 1:12 PM, Andy Lutomirski wrote: >> >> >>> On May 17, 2019, at 9:37 AM, Stephen Smalley <sds@tycho.nsa.gov> wrote: >>> >>>> On 5/17/19 12:20 PM, Stephen Smalley wrote: >>>>> On 5/17/19 11:09 AM, Sean Christopherson wrote: >>>>>> On Fri, May 17, 2019 at 09:53:06AM -0400, Stephen Smalley wrote: >>>>>>> On 5/16/19 6:23 PM, Xing, Cedric wrote: >>>>>>> I thought EXECMOD applied to files (and memory mappings backed by >>>>>>> them) but >>>>>>> I was probably wrong. It sounds like EXECMOD applies to the whole >>>>>>> process so >>>>>>> would allow all pages within a process's address space to be >>>>>>> modified then >>>>>>> executed, regardless the backing files. Am I correct this time? >>>>>> >>>>>> No, you were correct the first time I think; EXECMOD is used to >>>>>> control >>>>>> whether a process can make executable a private file mapping that has >>>>>> previously been modified (e.g. text relocation); it is a special >>>>>> case to >>>>>> support text relocations without having to allow full EXECMEM >>>>>> (i.e. execute >>>>>> arbitrary memory). >>>>>> >>>>>> SELinux checks relevant to W^X include: >>>>>> >>>>>> - EXECMEM: mmap/mprotect PROT_EXEC an anonymous mapping >>>>>> (regardless of >>>>>> PROT_WRITE, since we know the content has to have been written at >>>>>> some >>>>>> point) or a private file mapping that is also PROT_WRITE. >>>>>> - EXECMOD: mprotect PROT_EXEC a private file mapping that has been >>>>>> previously modified, typically for text relocations, >>>>>> - FILE__WRITE: mmap/mprotect PROT_WRITE a shared file mapping, >>>>>> - FILE__EXECUTE: mmap/mprotect PROT_EXEC a file mapping. >>>>>> >>>>>> (ignoring EXECSTACK and EXECHEAP here since they aren't really >>>>>> relevant to >>>>>> this discussion) >>>>>> >>>>>> So if you want to ensure W^X, then you wouldn't allow EXECMEM for the >>>>>> process, EXECMOD by the process to any file, and the combination >>>>>> of both >>>>>> FILE__WRITE and FILE__EXECUTE by the process to any file. >>>>>> >>>>>> If the /dev/sgx/enclave mappings are MAP_SHARED and you aren't >>>>>> using an >>>>>> anonymous inode, then I would expect that only the FILE__WRITE and >>>>>> FILE__EXECUTE checks are relevant. >>>>> >>>>> Yep, I was just typing this up in a different thread: >>>>> >>>>> I think we may want to change the SGX API to alloc an anon inode >>>>> for each >>>>> enclave instead of hanging every enclave off of the >>>>> /dev/sgx/enclave inode. >>>>> Because /dev/sgx/enclave is NOT private, SELinux's >>>>> file_map_prot_check() >>>>> will only require FILE__WRITE and FILE__EXECUTE to mprotect() >>>>> enclave VMAs >>>>> to RWX. Backing each enclave with an anon inode will make SELinux >>>>> treat >>>>> EPC memory like anonymous mappings, which is what we want (I >>>>> think), e.g. >>>>> making *any* EPC page executable will require PROCESS__EXECMEM (SGX is >>>>> 64-bit only at this point, so SELinux will always have >>>>> default_noexec). >>>> I don't think we want to require EXECMEM (or equivalently both >>>> FILE__WRITE and FILE__EXECUTE to /dev/sgx/enclave) for making any >>>> EPC page executable, only if the page is also writable or previously >>>> modified. The intent is to prevent arbitrary code execution without >>>> EXECMEM (or FILE__WRITE|FILE__EXECUTE), while still allowing >>>> enclaves to be created without EXECMEM as long as the EPC page >>>> mapping is only ever mapped RX and its initial contents came from an >>>> unmodified file mapping that was PROT_EXEC (and hence already >>>> checked via FILE__EXECUTE). >>> >>> Also, just to be clear, there is nothing inherently better about >>> checking EXECMEM instead of checking both FILE__WRITE and >>> FILE__EXECUTE to the /dev/sgx/enclave inode, so I wouldn't switch to >>> using anon inodes for that reason. Using anon inodes also >>> unfortunately disables SELinux inode-based checking since we no >>> longer have any useful inode information, so you'd lose out on >>> SELinux ioctl whitelisting on those enclave inodes if that matters. >> >> How can that work? Unless the API changes fairly radically, users >> fundamentally need to both write and execute the enclave. Some of it >> will be written only from already executable pages, and some privilege >> should be needed to execute any enclave page that was not loaded like >> this. > > I'm not sure what the API is. Let's say they do something like this: > > fd = open("/dev/sgx/enclave", O_RDONLY); > addr = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0); > stuff addr into ioctl args > ioctl(fd, ENCLAVE_CREATE, &ioctlargs); > ioctl(fd, ENCLAVE_ADD_PAGE, &ioctlargs); > ioctl(fd, ENCLAVE_INIT, &ioctlargs); > > The important points are that they do not open /dev/sgx/enclave with > write access (otherwise they will trigger FILE__WRITE at open time, and > later encounter FILE__EXECUTE as well during mmap, thereby requiring > both to be allowed to /dev/sgx/enclave), and that they do not request > PROT_WRITE to the resulting mapping (otherwise they will trigger > FILE__WRITE at mmap time). Then only FILE__READ and FILE__EXECUTE are > required to /dev/sgx/enclave in policy. > > If they switch to an anon inode, then any mmap PROT_EXEC of the opened > file will trigger an EXECMEM check, at least as currently implemented, > as we have no useful backing inode information. FWIW, looking at the selftest for SGX in the patch series, they open /dev/sgx/enclave O_RDWR (probably not necessary?) and mmap the open file RWX. If that is necessary then I'd rather it show up as FILE__WRITE and FILE__EXECUTE to /dev/sgx/enclave instead of EXECMEM, so that we can allow the process the ability to perform that mmap without allowing it to make other mappings WX. So staying with the single /dev/sgx/enclave inode is better in that regard.
On Fri, May 17, 2019 at 02:05:39PM -0400, Stephen Smalley wrote: > On 5/17/19 1:12 PM, Andy Lutomirski wrote: > > > >How can that work? Unless the API changes fairly radically, users > >fundamentally need to both write and execute the enclave. Some of it will > >be written only from already executable pages, and some privilege should be > >needed to execute any enclave page that was not loaded like this. > > I'm not sure what the API is. Let's say they do something like this: > > fd = open("/dev/sgx/enclave", O_RDONLY); > addr = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0); > stuff addr into ioctl args > ioctl(fd, ENCLAVE_CREATE, &ioctlargs); > ioctl(fd, ENCLAVE_ADD_PAGE, &ioctlargs); > ioctl(fd, ENCLAVE_INIT, &ioctlargs); That's rougly the flow, except that that all enclaves need to have RW and X EPC pages. > The important points are that they do not open /dev/sgx/enclave with write > access (otherwise they will trigger FILE__WRITE at open time, and later > encounter FILE__EXECUTE as well during mmap, thereby requiring both to be > allowed to /dev/sgx/enclave), and that they do not request PROT_WRITE to the > resulting mapping (otherwise they will trigger FILE__WRITE at mmap time). > Then only FILE__READ and FILE__EXECUTE are required to /dev/sgx/enclave in > policy. > > If they switch to an anon inode, then any mmap PROT_EXEC of the opened file > will trigger an EXECMEM check, at least as currently implemented, as we have > no useful backing inode information. Yep, and that's by design in the overall proposal. The trick is that ENCLAVE_ADD takes a source VMA and copies the contents *and* the permissions from the source VMA. The source VMA points at regular memory that was mapped and populated using existing mechanisms for loading DSOs. E.g. at a high level: source_fd = open("/home/sean/path/to/my/enclave", O_RDONLY); for_each_chunk { <hand waving - mmap()/mprotect() the enclave file into regular memory> } enclave_fd = open("/dev/sgx/enclave", O_RDWR); /* allocs anon inode */ enclave_addr = mmap(NULL, size, PROT_READ, MAP_SHARED, enclave_fd, 0); ioctl(enclave_fd, ENCLAVE_CREATE, {enclave_addr}); for_each_chunk { struct sgx_enclave_add ioctlargs = { .offset = chunk.offset, .source = chunk.addr, .size = chunk.size, .type = chunk.type, /* SGX specific metadata */ } ioctl(fd, ENCLAVE_ADD, &ioctlargs); /* modifies enclave's VMAs */ } ioctl(fd, ENCLAVE_INIT, ...); Userspace never explicitly requests PROT_EXEC on enclave_fd, but SGX also ensures userspace isn't bypassing LSM policies by virtue of copying the permissions for EPC VMAs from regular VMAs that have already gone through LSM checks.
On 5/17/19 3:28 PM, Sean Christopherson wrote: > On Fri, May 17, 2019 at 02:05:39PM -0400, Stephen Smalley wrote: >> On 5/17/19 1:12 PM, Andy Lutomirski wrote: >>> >>> How can that work? Unless the API changes fairly radically, users >>> fundamentally need to both write and execute the enclave. Some of it will >>> be written only from already executable pages, and some privilege should be >>> needed to execute any enclave page that was not loaded like this. >> >> I'm not sure what the API is. Let's say they do something like this: >> >> fd = open("/dev/sgx/enclave", O_RDONLY); >> addr = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0); >> stuff addr into ioctl args >> ioctl(fd, ENCLAVE_CREATE, &ioctlargs); >> ioctl(fd, ENCLAVE_ADD_PAGE, &ioctlargs); >> ioctl(fd, ENCLAVE_INIT, &ioctlargs); > > That's rougly the flow, except that that all enclaves need to have RW and > X EPC pages. > >> The important points are that they do not open /dev/sgx/enclave with write >> access (otherwise they will trigger FILE__WRITE at open time, and later >> encounter FILE__EXECUTE as well during mmap, thereby requiring both to be >> allowed to /dev/sgx/enclave), and that they do not request PROT_WRITE to the >> resulting mapping (otherwise they will trigger FILE__WRITE at mmap time). >> Then only FILE__READ and FILE__EXECUTE are required to /dev/sgx/enclave in >> policy. >> >> If they switch to an anon inode, then any mmap PROT_EXEC of the opened file >> will trigger an EXECMEM check, at least as currently implemented, as we have >> no useful backing inode information. > > Yep, and that's by design in the overall proposal. The trick is that > ENCLAVE_ADD takes a source VMA and copies the contents *and* the > permissions from the source VMA. The source VMA points at regular memory > that was mapped and populated using existing mechanisms for loading DSOs. > > E.g. at a high level: > > source_fd = open("/home/sean/path/to/my/enclave", O_RDONLY); > for_each_chunk { > <hand waving - mmap()/mprotect() the enclave file into regular memory> > } > > enclave_fd = open("/dev/sgx/enclave", O_RDWR); /* allocs anon inode */ > enclave_addr = mmap(NULL, size, PROT_READ, MAP_SHARED, enclave_fd, 0); > > ioctl(enclave_fd, ENCLAVE_CREATE, {enclave_addr}); > for_each_chunk { > struct sgx_enclave_add ioctlargs = { > .offset = chunk.offset, > .source = chunk.addr, > .size = chunk.size, > .type = chunk.type, /* SGX specific metadata */ > } > ioctl(fd, ENCLAVE_ADD, &ioctlargs); /* modifies enclave's VMAs */ > } > ioctl(fd, ENCLAVE_INIT, ...); > > > Userspace never explicitly requests PROT_EXEC on enclave_fd, but SGX also > ensures userspace isn't bypassing LSM policies by virtue of copying the > permissions for EPC VMAs from regular VMAs that have already gone through > LSM checks. Is O_RDWR required for /dev/sgx/enclave or would O_RDONLY suffice? Do you do anything other than ioctl() calls on it? What's the advantage of allocating an anon inode in the above? At present anon inodes are exempted from inode-based checking, thereby losing the ability to perform SELinux ioctl whitelisting, unlike the file-backed /dev/sgx/enclave inode. How would SELinux (or other security modules) restrict the authorized enclaves that can be loaded via this interface? Would the sgx driver invoke a new LSM hook with the regular/source VMAs as parameters and allow the security module to reject the ENCLAVE_ADD operation? That could be just based on the vm_file (e.g. whitelist what enclave files are permitted in general) or it could be based on both the process and the vm_file (e.g. only allow specific enclaves to be loaded into specific processes).
> On May 17, 2019, at 1:09 PM, Stephen Smalley <sds@tycho.nsa.gov> wrote: > >> On 5/17/19 3:28 PM, Sean Christopherson wrote: >>> On Fri, May 17, 2019 at 02:05:39PM -0400, Stephen Smalley wrote: >>>> On 5/17/19 1:12 PM, Andy Lutomirski wrote: >>>> >>>> How can that work? Unless the API changes fairly radically, users >>>> fundamentally need to both write and execute the enclave. Some of it will >>>> be written only from already executable pages, and some privilege should be >>>> needed to execute any enclave page that was not loaded like this. >>> >>> I'm not sure what the API is. Let's say they do something like this: >>> >>> fd = open("/dev/sgx/enclave", O_RDONLY); >>> addr = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0); >>> stuff addr into ioctl args >>> ioctl(fd, ENCLAVE_CREATE, &ioctlargs); >>> ioctl(fd, ENCLAVE_ADD_PAGE, &ioctlargs); >>> ioctl(fd, ENCLAVE_INIT, &ioctlargs); >> That's rougly the flow, except that that all enclaves need to have RW and >> X EPC pages. >>> The important points are that they do not open /dev/sgx/enclave with write >>> access (otherwise they will trigger FILE__WRITE at open time, and later >>> encounter FILE__EXECUTE as well during mmap, thereby requiring both to be >>> allowed to /dev/sgx/enclave), and that they do not request PROT_WRITE to the >>> resulting mapping (otherwise they will trigger FILE__WRITE at mmap time). >>> Then only FILE__READ and FILE__EXECUTE are required to /dev/sgx/enclave in >>> policy. >>> >>> If they switch to an anon inode, then any mmap PROT_EXEC of the opened file >>> will trigger an EXECMEM check, at least as currently implemented, as we have >>> no useful backing inode information. >> Yep, and that's by design in the overall proposal. The trick is that >> ENCLAVE_ADD takes a source VMA and copies the contents *and* the >> permissions from the source VMA. The source VMA points at regular memory >> that was mapped and populated using existing mechanisms for loading DSOs. >> E.g. at a high level: >> source_fd = open("/home/sean/path/to/my/enclave", O_RDONLY); >> for_each_chunk { >> <hand waving - mmap()/mprotect() the enclave file into regular memory> >> } >> enclave_fd = open("/dev/sgx/enclave", O_RDWR); /* allocs anon inode */ >> enclave_addr = mmap(NULL, size, PROT_READ, MAP_SHARED, enclave_fd, 0); >> ioctl(enclave_fd, ENCLAVE_CREATE, {enclave_addr}); >> for_each_chunk { >> struct sgx_enclave_add ioctlargs = { >> .offset = chunk.offset, >> .source = chunk.addr, >> .size = chunk.size, >> .type = chunk.type, /* SGX specific metadata */ >> } >> ioctl(fd, ENCLAVE_ADD, &ioctlargs); /* modifies enclave's VMAs */ >> } >> ioctl(fd, ENCLAVE_INIT, ...); >> Userspace never explicitly requests PROT_EXEC on enclave_fd, but SGX also >> ensures userspace isn't bypassing LSM policies by virtue of copying the >> permissions for EPC VMAs from regular VMAs that have already gone through >> LSM checks. > > Is O_RDWR required for /dev/sgx/enclave or would O_RDONLY suffice? Do you do anything other than ioctl() calls on it? > > What's the advantage of allocating an anon inode in the above? At present anon inodes are exempted from inode-based checking, thereby losing the ability to perform SELinux ioctl whitelisting, unlike the file-backed /dev/sgx/enclave inode. > > How would SELinux (or other security modules) restrict the authorized enclaves that can be loaded via this interface? Would the sgx driver invoke a new LSM hook with the regular/source VMAs as parameters and allow the security module to reject the ENCLAVE_ADD operation? That could be just based on the vm_file (e.g. whitelist what enclave files are permitted in general) or it could be based on both the process and the vm_file (e.g. only allow specific enclaves to be loaded into specific processes). This is the idea behind the .sigstruct file. The driver could call a new hook to approve or reject the .sigstruct. The sigstruct contains a hash of the whole enclave and a signature by the author.
On 5/17/19 4:14 PM, Andy Lutomirski wrote: > >> On May 17, 2019, at 1:09 PM, Stephen Smalley <sds@tycho.nsa.gov> wrote: >> >>> On 5/17/19 3:28 PM, Sean Christopherson wrote: >>>> On Fri, May 17, 2019 at 02:05:39PM -0400, Stephen Smalley wrote: >>>>> On 5/17/19 1:12 PM, Andy Lutomirski wrote: >>>>> >>>>> How can that work? Unless the API changes fairly radically, users >>>>> fundamentally need to both write and execute the enclave. Some of it will >>>>> be written only from already executable pages, and some privilege should be >>>>> needed to execute any enclave page that was not loaded like this. >>>> >>>> I'm not sure what the API is. Let's say they do something like this: >>>> >>>> fd = open("/dev/sgx/enclave", O_RDONLY); >>>> addr = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0); >>>> stuff addr into ioctl args >>>> ioctl(fd, ENCLAVE_CREATE, &ioctlargs); >>>> ioctl(fd, ENCLAVE_ADD_PAGE, &ioctlargs); >>>> ioctl(fd, ENCLAVE_INIT, &ioctlargs); >>> That's rougly the flow, except that that all enclaves need to have RW and >>> X EPC pages. >>>> The important points are that they do not open /dev/sgx/enclave with write >>>> access (otherwise they will trigger FILE__WRITE at open time, and later >>>> encounter FILE__EXECUTE as well during mmap, thereby requiring both to be >>>> allowed to /dev/sgx/enclave), and that they do not request PROT_WRITE to the >>>> resulting mapping (otherwise they will trigger FILE__WRITE at mmap time). >>>> Then only FILE__READ and FILE__EXECUTE are required to /dev/sgx/enclave in >>>> policy. >>>> >>>> If they switch to an anon inode, then any mmap PROT_EXEC of the opened file >>>> will trigger an EXECMEM check, at least as currently implemented, as we have >>>> no useful backing inode information. >>> Yep, and that's by design in the overall proposal. The trick is that >>> ENCLAVE_ADD takes a source VMA and copies the contents *and* the >>> permissions from the source VMA. The source VMA points at regular memory >>> that was mapped and populated using existing mechanisms for loading DSOs. >>> E.g. at a high level: >>> source_fd = open("/home/sean/path/to/my/enclave", O_RDONLY); >>> for_each_chunk { >>> <hand waving - mmap()/mprotect() the enclave file into regular memory> >>> } >>> enclave_fd = open("/dev/sgx/enclave", O_RDWR); /* allocs anon inode */ >>> enclave_addr = mmap(NULL, size, PROT_READ, MAP_SHARED, enclave_fd, 0); >>> ioctl(enclave_fd, ENCLAVE_CREATE, {enclave_addr}); >>> for_each_chunk { >>> struct sgx_enclave_add ioctlargs = { >>> .offset = chunk.offset, >>> .source = chunk.addr, >>> .size = chunk.size, >>> .type = chunk.type, /* SGX specific metadata */ >>> } >>> ioctl(fd, ENCLAVE_ADD, &ioctlargs); /* modifies enclave's VMAs */ >>> } >>> ioctl(fd, ENCLAVE_INIT, ...); >>> Userspace never explicitly requests PROT_EXEC on enclave_fd, but SGX also >>> ensures userspace isn't bypassing LSM policies by virtue of copying the >>> permissions for EPC VMAs from regular VMAs that have already gone through >>> LSM checks. >> >> Is O_RDWR required for /dev/sgx/enclave or would O_RDONLY suffice? Do you do anything other than ioctl() calls on it? >> >> What's the advantage of allocating an anon inode in the above? At present anon inodes are exempted from inode-based checking, thereby losing the ability to perform SELinux ioctl whitelisting, unlike the file-backed /dev/sgx/enclave inode. >> >> How would SELinux (or other security modules) restrict the authorized enclaves that can be loaded via this interface? Would the sgx driver invoke a new LSM hook with the regular/source VMAs as parameters and allow the security module to reject the ENCLAVE_ADD operation? That could be just based on the vm_file (e.g. whitelist what enclave files are permitted in general) or it could be based on both the process and the vm_file (e.g. only allow specific enclaves to be loaded into specific processes). > > This is the idea behind the .sigstruct file. The driver could call a new hook to approve or reject the .sigstruct. The sigstruct contains a hash of the whole enclave and a signature by the author. Ok, so same idea but moved to ENCLAVE_INIT and passing the vma or file for the sigstruct instead of the enclave.
On Fri, May 17, 2019 at 04:09:22PM -0400, Stephen Smalley wrote: > On 5/17/19 3:28 PM, Sean Christopherson wrote: > >On Fri, May 17, 2019 at 02:05:39PM -0400, Stephen Smalley wrote: > >Yep, and that's by design in the overall proposal. The trick is that > >ENCLAVE_ADD takes a source VMA and copies the contents *and* the > >permissions from the source VMA. The source VMA points at regular memory > >that was mapped and populated using existing mechanisms for loading DSOs. > > > >E.g. at a high level: > > > >source_fd = open("/home/sean/path/to/my/enclave", O_RDONLY); > >for_each_chunk { > > <hand waving - mmap()/mprotect() the enclave file into regular memory> > >} > > > >enclave_fd = open("/dev/sgx/enclave", O_RDWR); /* allocs anon inode */ > >enclave_addr = mmap(NULL, size, PROT_READ, MAP_SHARED, enclave_fd, 0); > > > >ioctl(enclave_fd, ENCLAVE_CREATE, {enclave_addr}); > >for_each_chunk { > > struct sgx_enclave_add ioctlargs = { > > .offset = chunk.offset, > > .source = chunk.addr, > > .size = chunk.size, > > .type = chunk.type, /* SGX specific metadata */ > > } > > ioctl(fd, ENCLAVE_ADD, &ioctlargs); /* modifies enclave's VMAs */ > >} > >ioctl(fd, ENCLAVE_INIT, ...); > > > > > >Userspace never explicitly requests PROT_EXEC on enclave_fd, but SGX also > >ensures userspace isn't bypassing LSM policies by virtue of copying the > >permissions for EPC VMAs from regular VMAs that have already gone through > >LSM checks. > > Is O_RDWR required for /dev/sgx/enclave or would O_RDONLY suffice? Do you > do anything other than ioctl() calls on it? Hmm, in the current implementation, yes, O_RDWR is required. An enclave and its associated EPC memory are represented and referenced by its fd, which is backed by /dev/sgx/enclave. An enclave is not just code, e.g. also has a heap, stack, variables, etc..., which need to be mapped accordingly. In the current implementation, userspace directly does mprotect() or mmap() on EPC VMAs, and so setting PROT_WRITE for the heap and whatnot requires opening /dev/sgx/enclave with O_RDWR. I *think* /dev/sgx/enclave could be opened O_RDONLY if ENCLAVE_ADD stuffed the EPC VMA permissions, assuming the use case doesn't require changing permissions after the enclave has been created. The other reason userspace would need to open /dev/sgx/enclave O_RDWR would be to debug an enclave, e.g. pwrite() works on the enclave fd due to SGX restrictions on modifying EPC memory from outside the enclave. But that's an obvious case where FILE__WRITE should be required. > What's the advantage of allocating an anon inode in the above? At present > anon inodes are exempted from inode-based checking, thereby losing the > ability to perform SELinux ioctl whitelisting, unlike the file-backed > /dev/sgx/enclave inode. Purely to trigger the EXECMEM check on any PROT_EXEC mapping. However, the motiviation for that was due to my bad assumption that FILE__WRITE and FILE__EXECUTE are global and not per process. If we can do as you suggest and allow creation of enclaves with O_RDONLY, then keeping a file-backed inode is definitely better as it means most processes only need FILE__READ and FILE__* in general has actual meaning. Thanks a bunch for your help!
On Thu, May 16, 2019 at 05:24:33PM +1000, James Morris wrote: Good morning, I hope everyone had a pleasant weekend. James, I believe the last time our paths crossed was at the Linux Security Summit in Seattle, I trust you have been well since then. > On Wed, 15 May 2019, Andy Lutomirski wrote: > > > On Wed, May 15, 2019 at 3:46 PM James Morris <jmorris@namei.org> wrote: > > > > > > You could try user.sigstruct, which does not require any privs. > > > > > > > I don't think I understand your proposal. What file would this > > attribute be on? What would consume it? > It would be on the enclave file, so you keep the sigstruct bound to > it, rather than needing a separate file to manage. It would > simplify any LSM policy check. > > It would be consumed by (I guess) the SGX_INIT_THE_ENCLAVE ioctl in your > example, instead of having a 2nd fd. I've watched this discussion regarding LSM, sigstructs and file descriptors with some fascination, since all of this infrastructure already exists and should be well understood by anyone who has been active in SGX runtime development. There would thus seem to be a disconnect between SGX driver developers and the consumers of the services of the driver. The existing enclave format, codified by the silo within Intel that is responsible for the existing SDK/PSW, implements a notes section stored inside a standard ELF shared library image. The notes section contains a significant amount of metadata that is used to direct the instantiation of what will be the initialized enclave image. Said metadata includes a copy of the sigstruct that was generated when the enclave was signed, which is the event that triggers metadata generation. All of this means that any enclave that gets loaded effectively triggers both LSM and IMA checks. James, if you remember, the paper that we presented in Seattle described the initial implementation of an extension to the Linux IMA infrastructure that tracks whether or not processes can be 'trusted'. That work has gone on to include running the trust modeling and disciplining engine inside of a namespace specific SGX enclave. We would be happy to make available execution trajectory logs that clearly document IMA and LSM checks being conducted on enclaves. There is a strong probability that we will be maintaining and supporting a modified version of whatever driver that goes upstream. In support of this we are putting together a white paper discussing security architecture concerns inherent in an SGX driver. With the intent of avoiding LKML verbosity we will post a URL to the paper when it is available if there is interest. The issue of EDMM has already come up, suffice it to say that EDMM makes LSM inspection of enclave content, while desirable, largely irrelevant from a security perspective. > James Morris Best wishes for a productive week. Dr. Greg As always, Dr. G.W. Wettstein, Ph.D. Enjellic Systems Development, LLC. 4206 N. 19th Ave. Specializing in information infra-structure Fargo, ND 58102 development. PH: 701-281-1686 EMAIL: greg@enjellic.com ------------------------------------------------------------------------------ "If you plugged up your nose and mouth right before you sneezed, would the sneeze go out your ears or would your head explode? Either way I'm afraid to try." -- Nick Kean
On Thu, May 16, 2019 at 03:45:50PM -0700, Sean Christopherson wrote: > On Thu, May 16, 2019 at 02:02:58PM -0700, Andy Lutomirski wrote: > > > On May 15, 2019, at 10:16 PM, Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > > > There is a problem here though. Usually the enclave itself is just a > > > loader that then loads the application from outside source and creates > > > the executable pages from the content. > > > > > > A great example of this is Graphene that bootstraps unmodified Linux > > > applications to an enclave: > > > > > > https://github.com/oscarlab/graphene > > > > > > > ISTM you should need EXECMEM or similar to run Graphene, then. > > Agreed, Graphene is effectively running arbitrary enclave code. I'm > guessing there is nothing that prevents extending/reworking Graphene to > allow generating the enclave ahead of time so as to avoid populating the > guts of the enclave at runtime, i.e. it's likely possible to run an > unmodified application in an enclave without EXECMEM if that's something > Graphene or its users really care about. I'd guess that also people adding SGX support to containers want somewhat similar framework to work on so that you can just wrap a container with an enclave. /Jarkko
On Thu, May 16, 2019 at 02:02:58PM -0700, Andy Lutomirski wrote: > That certainly *could* be done, and I guess the decision could be left > to the LSMs, but I'm not convinced this adds value. What security use > case does this cover that isn't already covered by requiring EXECUTE > (e.g. lib_t) on the enclave file and some new SIGSTRUCT right on the > .sigstruct? I guess you are right as SIGSTRUCT completely shields the memory layout and contents of an enclave. /Jarkko
On Thu, May 16, 2019 at 05:03:31PM -0700, Sean Christopherson wrote: > The SGX ioctl() would need to take mmap_sem for write, but we can mitigate > that issue by changing the ioctl() to take a range of memory instead of a > single page. That'd also provide "EADD batching" that folks have > requested. This should be easy enough to add as the EADD operations are already batched internally to a worker thread. /Jarkko
On Thu, May 16, 2019 at 05:26:15PM -0700, Andy Lutomirski wrote:
> Is userspace actually requred to mmap() the enclave prior to EADDing things?
Nope, not since v20. Here is what I wrote about API to the kernel
documentation:
"The enclave life-cycle starts by opening `/dev/sgx/enclave`. After this
there is already a data structure inside kernel tracking the enclave
that is initially uncreated. After this a set of ioctl's can be used to
create, populate and initialize the enclave.
You can close (if you want) the fd after you've mmap()'d. As long as the
file is open the enclave stays alive so you might want to do that after
you don't need it anymore. Even munmap() won't destruct the enclave if
the file is open. Neither will closing the fd as long as you have
mmap() done over the fd (even if it does not across the range defined in
SECS)."
Enclave can be created and initialized without doing a single mmap()
call.
/Jarkko
On Fri, May 17, 2019 at 08:41:28AM -0700, Sean Christopherson wrote: > It was a requirement prior to the API rework in v20, i.e. unless someone > was really quick on the draw after the v20 update all existing userspace > implementations mmap() the enclave before ECREATE. Requiring a valid > enclave VMA for EADD shoudn't be too onerous. Still underlining: it is not required. /Jarkko
On Mon, May 20, 2019 at 02:41:05PM +0300, Jarkko Sakkinen wrote: > On Thu, May 16, 2019 at 05:26:15PM -0700, Andy Lutomirski wrote: > > Is userspace actually requred to mmap() the enclave prior to EADDing things? > > Nope, not since v20. Here is what I wrote about API to the kernel > documentation: > > "The enclave life-cycle starts by opening `/dev/sgx/enclave`. After this > there is already a data structure inside kernel tracking the enclave > that is initially uncreated. After this a set of ioctl's can be used to > create, populate and initialize the enclave. > > You can close (if you want) the fd after you've mmap()'d. As long as the > file is open the enclave stays alive so you might want to do that after > you don't need it anymore. Even munmap() won't destruct the enclave if > the file is open. Neither will closing the fd as long as you have > mmap() done over the fd (even if it does not across the range defined in > SECS)." > > Enclave can be created and initialized without doing a single mmap() > call. We could even disallow mmap() before EINIT done. The way enclave management internally works right now is quite robust and completely detached from requiring process address space for anything. /Jarkko
On 2019-05-21 08:19, Jarkko Sakkinen wrote:
> We could even disallow mmap() before EINIT done.
This would be extremely annoying in software because now you have to
save the all the page permissions somewhere between EADD and mprotect.
--
Jethro Beekman | Fortanix
On Tue, May 21, 2019 at 06:19:37PM +0300, Jarkko Sakkinen wrote: > On Mon, May 20, 2019 at 02:41:05PM +0300, Jarkko Sakkinen wrote: > > On Thu, May 16, 2019 at 05:26:15PM -0700, Andy Lutomirski wrote: > > > Is userspace actually requred to mmap() the enclave prior to EADDing things? > > > > Nope, not since v20. Here is what I wrote about API to the kernel > > documentation: > > > > "The enclave life-cycle starts by opening `/dev/sgx/enclave`. After this > > there is already a data structure inside kernel tracking the enclave > > that is initially uncreated. After this a set of ioctl's can be used to > > create, populate and initialize the enclave. > > > > You can close (if you want) the fd after you've mmap()'d. As long as the > > file is open the enclave stays alive so you might want to do that after > > you don't need it anymore. Even munmap() won't destruct the enclave if > > the file is open. Neither will closing the fd as long as you have > > mmap() done over the fd (even if it does not across the range defined in > > SECS)." > > > > Enclave can be created and initialized without doing a single mmap() > > call. > > We could even disallow mmap() before EINIT done. The way enclave > management internally works right now is quite robust and completely > detached from requiring process address space for anything. Except that mmap() is more or less required to guarantee that ELRANGE established by ECREATE is available. And we want to disallow mmap() as soon as the first EADD is done so that userspace can't remap the enclave's VMAs via munmap()->mmap() and gain execute permissions to pages that were EADD'd as NX. Actually, conceptually it's probably more intuitive to disallow mmap() at ECREATE, i.e. the act of creating an enclave pins the associated virtual address range until the enclave is destroyed.
On Tue, May 21, 2019 at 03:24:18PM +0000, Jethro Beekman wrote: > On 2019-05-21 08:19, Jarkko Sakkinen wrote: > > We could even disallow mmap() before EINIT done. > This would be extremely annoying in software because now you have to save > the all the page permissions somewhere between EADD and mprotect. Actually you don't have to use mprotect anymore that much. You can just do multiple mmap's even with v20 after EINIT, one for each region (albeit it does not enforce above). /Jarkko
On Tue, May 21, 2019 at 08:51:40AM -0700, Sean Christopherson wrote: > Except that mmap() is more or less required to guarantee that ELRANGE > established by ECREATE is available. And we want to disallow mmap() as > soon as the first EADD is done so that userspace can't remap the enclave's > VMAs via munmap()->mmap() and gain execute permissions to pages that were > EADD'd as NX. We don't want to guarantee such thing and it is not guaranteed. It does not fit at all to the multi process work done. Enclaves are detached from any particular process addresse spaces. It is responsibility of process to open windows to them. That would be completely against work that we've done lately. > Actually, conceptually it's probably more intuitive to disallow mmap() at > ECREATE, i.e. the act of creating an enclave pins the associated virtual > address range until the enclave is destroyed. /Jarkko
On Wed, May 22, 2019 at 04:20:22PM +0300, Jarkko Sakkinen wrote: > On Tue, May 21, 2019 at 08:51:40AM -0700, Sean Christopherson wrote: > > Except that mmap() is more or less required to guarantee that ELRANGE > > established by ECREATE is available. And we want to disallow mmap() as > > soon as the first EADD is done so that userspace can't remap the enclave's > > VMAs via munmap()->mmap() and gain execute permissions to pages that were > > EADD'd as NX. > > We don't want to guarantee such thing and it is not guaranteed. It does > not fit at all to the multi process work done. Enclaves are detached > from any particular process addresse spaces. It is responsibility of > process to open windows to them. > > That would be completely against work that we've done lately. Example use case: you have a process that just constructs an enclave and sends it to another process or processes for use. The constructor process could have basically anything on that range. This was the key goal of the fd based enclave work. /Jarkko
On 5/22/19 9:22 AM, Jarkko Sakkinen wrote: > On Wed, May 22, 2019 at 04:20:22PM +0300, Jarkko Sakkinen wrote: >> On Tue, May 21, 2019 at 08:51:40AM -0700, Sean Christopherson wrote: >>> Except that mmap() is more or less required to guarantee that ELRANGE >>> established by ECREATE is available. And we want to disallow mmap() as >>> soon as the first EADD is done so that userspace can't remap the enclave's >>> VMAs via munmap()->mmap() and gain execute permissions to pages that were >>> EADD'd as NX. >> >> We don't want to guarantee such thing and it is not guaranteed. It does >> not fit at all to the multi process work done. Enclaves are detached >> from any particular process addresse spaces. It is responsibility of >> process to open windows to them. >> >> That would be completely against work that we've done lately. > > Example use case: you have a process that just constructs an enclave > and sends it to another process or processes for use. The constructor > process could have basically anything on that range. This was the key > goal of the fd based enclave work. What exactly happens in the constructor versus the recipient processes? Which process performs each of the necessary open(), mmap(), and ioctl() calls for setting up the enclave? Can you provide a high level overview of the sequence of userspace calls by the constructor and by the recipient similar to what Sean showed earlier for just a single process?
On Wed, May 22, 2019 at 09:56:30AM -0400, Stephen Smalley wrote: > On 5/22/19 9:22 AM, Jarkko Sakkinen wrote: > >On Wed, May 22, 2019 at 04:20:22PM +0300, Jarkko Sakkinen wrote: > >>On Tue, May 21, 2019 at 08:51:40AM -0700, Sean Christopherson wrote: > >>>Except that mmap() is more or less required to guarantee that ELRANGE > >>>established by ECREATE is available. And we want to disallow mmap() as > >>>soon as the first EADD is done so that userspace can't remap the enclave's > >>>VMAs via munmap()->mmap() and gain execute permissions to pages that were > >>>EADD'd as NX. > >> > >>We don't want to guarantee such thing and it is not guaranteed. It does > >>not fit at all to the multi process work done. Enclaves are detached > >>from any particular process addresse spaces. It is responsibility of > >>process to open windows to them. > >> > >>That would be completely against work that we've done lately. > > > >Example use case: you have a process that just constructs an enclave > >and sends it to another process or processes for use. The constructor > >process could have basically anything on that range. This was the key > >goal of the fd based enclave work. > > What exactly happens in the constructor versus the recipient processes? > Which process performs each of the necessary open(), mmap(), and ioctl() > calls for setting up the enclave? Can you provide a high level overview of > the sequence of userspace calls by the constructor and by the recipient > similar to what Sean showed earlier for just a single process? Hmm, what we had talked about was allowing the SGX ioctls to work without an associated VMA, with the end goal of letting userspace restrict access to /dev/sgx/enclave. Very roughly... Enclave Owner: connect(builder, ...); send(builder, "/home/sean/path/to/my/enclave"); recv(builder, &enclave_fd); for_each_chunk { mmap(enclave_addr + offset, size, ..., MAP_SHARED, enclave_fd, 0); } Enclave Builder: recv(sock, &enclave_path); source_fd = open(enclave_path, O_RDONLY); for_each_chunk { <hand waving - mmap()/mprotect() the enclave file into regular memory> } enclave_fd = open("/dev/sgx/enclave", O_RDWR); ioctl(enclave_fd, ENCLAVE_CREATE, ...); for_each_chunk { struct sgx_enclave_add ioctlargs = { .offset = chunk.offset, .source = chunk.addr, .size = chunk.size, .type = chunk.type, /* SGX specific metadata */ } ioctl(fd, ENCLAVE_ADD, &ioctlargs); /* modifies enclave's VMAs */ } ioctl(enclave_fd, ENCLAVE_INIT, ...); write(sock, enclave_fd); But the above flow is flawed because there'a catch-22: ENCLAVE_ECREATE takes the virtual address of the enclave, but in the above flow that's not established until "mmap(..., enclave_fd)". And because an enclave's virtual range needs to be naturally aligned (hardware requirements), the enclave owner would need to do something like: source_fd = open("/home/sean/path/to/my/enclave", O_RDONLY); size = <parse size from source_fd> enclave_range = mmap(NULL, size*2, PROT_READ, ???, NULL, 0); enclave_addr = (enclave_range + (size - 1)) & ~(size - 1); connect(builder, ...); send(builder, {"/home/sean/path/to/my/enclave", enclave_addr}); recv(builder, &enclave_fd); munmap(enclave_range); for_each_chunk { addr = mmap(enclave_addr + c.offset, c.size, ..., MAP_SHARED, enclave_fd, 0); if (addr != enclave_addr + c.offset) exit(1); } And that straight up doesn't work with the v20 driver because mmap() with the enclave_fd will run through sgx_get_unmapped_area(), which also does the natural alignment adjustments (the idea being that mmap() is mapping the entire enclave). E.g. mmap() will map the wrong address if the offset of a chunk is less than its size due to the driver adjusting the address. Eliminating sgx_get_unmapped_area() means userspace is once again on the hook for naturally aligning the enclave, which is less than desirable. Looking back at the original API discussions around a builder process[1], we never fleshed out the end-to-end flow. While having a builder process *sounds* reasonable, in practice it adds a lot of complexity without providing much in the way of added security. E.g. in addition to the above mmap() issues, since the order of EADDs affects the enclave measurement, the enclave owner would need to communicate the exact steps to build the enclave, or the builder would need a priori knowledge of the enclave format. Userspace can still restrict access to /dev/sgx/enclave, e.g. by having a daemon that requires additional credentials to obtain a new enclave_fd. So AFAICT, the only benefit to having a dedicated builder is that it can do its own whitelisting of enclaves, but since we're trending towards supporting whitelisting enclaves in the kernel, e.g. via sigstruct, whitelisting in userspace purely in userspace also provides marginal value. TL;DR: Requiring VMA backing to build an enclave seems reasonable and sane. [1] https://lkml.kernel.org/r/CALCETrX+KisMCbptrnPSO79-YF4E3nR1XHt+a7hCs1GXsxAbtw@mail.gmail.com
On Wed, May 22, 2019 at 8:38 AM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Wed, May 22, 2019 at 09:56:30AM -0400, Stephen Smalley wrote: > > On 5/22/19 9:22 AM, Jarkko Sakkinen wrote: > > >On Wed, May 22, 2019 at 04:20:22PM +0300, Jarkko Sakkinen wrote: > > >>On Tue, May 21, 2019 at 08:51:40AM -0700, Sean Christopherson wrote: > > >>>Except that mmap() is more or less required to guarantee that ELRANGE > > >>>established by ECREATE is available. And we want to disallow mmap() as > > >>>soon as the first EADD is done so that userspace can't remap the enclave's > > >>>VMAs via munmap()->mmap() and gain execute permissions to pages that were > > >>>EADD'd as NX. > > >> > > >>We don't want to guarantee such thing and it is not guaranteed. It does > > >>not fit at all to the multi process work done. Enclaves are detached > > >>from any particular process addresse spaces. It is responsibility of > > >>process to open windows to them. > > >> > > >>That would be completely against work that we've done lately. > > > > > >Example use case: you have a process that just constructs an enclave > > >and sends it to another process or processes for use. The constructor > > >process could have basically anything on that range. This was the key > > >goal of the fd based enclave work. > > > > What exactly happens in the constructor versus the recipient processes? > > Which process performs each of the necessary open(), mmap(), and ioctl() > > calls for setting up the enclave? Can you provide a high level overview of > > the sequence of userspace calls by the constructor and by the recipient > > similar to what Sean showed earlier for just a single process? > > Hmm, what we had talked about was allowing the SGX ioctls to work without > an associated VMA, with the end goal of letting userspace restrict access > to /dev/sgx/enclave. Very roughly... > > Enclave Owner: > > connect(builder, ...); > send(builder, "/home/sean/path/to/my/enclave"); > > recv(builder, &enclave_fd); > > for_each_chunk { > mmap(enclave_addr + offset, size, ..., MAP_SHARED, enclave_fd, 0); > } > > > Enclave Builder: > > recv(sock, &enclave_path); > > source_fd = open(enclave_path, O_RDONLY); > for_each_chunk { > <hand waving - mmap()/mprotect() the enclave file into regular memory> > } > > enclave_fd = open("/dev/sgx/enclave", O_RDWR); > > ioctl(enclave_fd, ENCLAVE_CREATE, ...); > for_each_chunk { > struct sgx_enclave_add ioctlargs = { > .offset = chunk.offset, > .source = chunk.addr, > .size = chunk.size, > .type = chunk.type, /* SGX specific metadata */ > } > ioctl(fd, ENCLAVE_ADD, &ioctlargs); /* modifies enclave's VMAs */ > } > ioctl(enclave_fd, ENCLAVE_INIT, ...); > > write(sock, enclave_fd); > > > But the above flow is flawed because there'a catch-22: ENCLAVE_ECREATE > takes the virtual address of the enclave, but in the above flow that's > not established until "mmap(..., enclave_fd)". And because an enclave's > virtual range needs to be naturally aligned (hardware requirements), the > enclave owner would need to do something like: > > source_fd = open("/home/sean/path/to/my/enclave", O_RDONLY); > size = <parse size from source_fd> > > enclave_range = mmap(NULL, size*2, PROT_READ, ???, NULL, 0); > enclave_addr = (enclave_range + (size - 1)) & ~(size - 1); > > connect(builder, ...); > send(builder, {"/home/sean/path/to/my/enclave", enclave_addr}); > > recv(builder, &enclave_fd); > > munmap(enclave_range); > > for_each_chunk { > addr = mmap(enclave_addr + c.offset, c.size, ..., MAP_SHARED, enclave_fd, 0); > if (addr != enclave_addr + c.offset) > exit(1); > } > > And that straight up doesn't work with the v20 driver because mmap() with > the enclave_fd will run through sgx_get_unmapped_area(), which also does > the natural alignment adjustments (the idea being that mmap() is mapping > the entire enclave). E.g. mmap() will map the wrong address if the offset > of a chunk is less than its size due to the driver adjusting the address. That presumably needs to change. Are we entirely missing an API to allocate a naturally aligned VA range? That's kind of annoying. > > Eliminating sgx_get_unmapped_area() means userspace is once again on the > hook for naturally aligning the enclave, which is less than desirable. > > Looking back at the original API discussions around a builder process[1], > we never fleshed out the end-to-end flow. While having a builder process > *sounds* reasonable, in practice it adds a lot of complexity without > providing much in the way of added security. E.g. in addition to the > above mmap() issues, since the order of EADDs affects the enclave > measurement, the enclave owner would need to communicate the exact steps > to build the enclave, or the builder would need a priori knowledge of the > enclave format. > > Userspace can still restrict access to /dev/sgx/enclave, e.g. by having a > daemon that requires additional credentials to obtain a new enclave_fd. > So AFAICT, the only benefit to having a dedicated builder is that it can > do its own whitelisting of enclaves, but since we're trending towards > supporting whitelisting enclaves in the kernel, e.g. via sigstruct, > whitelisting in userspace purely in userspace also provides marginal value. > > TL;DR: Requiring VMA backing to build an enclave seems reasonable and sane. This isn't necessarily a problem, but we pretty much have to use mprotect() then. Maybe the semantics could just be that mmap() on the SGX device gives natural alignment, but that there is no actual constraint enforced by the driver as to whether mmap() happens before or after ECREATE. After all, it's *ugly* for user code to reserve its address range with an awkward giant mmap(), there's nothing fundamentally wrong with it. As far as I know from this whole discussion, we still haven't come up with any credible way to avoid tracking, per enclave page, whether that page came from unmodified PROT_EXEC memory.
On Wed, May 22, 2019 at 03:42:45PM -0700, Andy Lutomirski wrote: > On Wed, May 22, 2019 at 8:38 AM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > And that straight up doesn't work with the v20 driver because mmap() with > > the enclave_fd will run through sgx_get_unmapped_area(), which also does > > the natural alignment adjustments (the idea being that mmap() is mapping > > the entire enclave). E.g. mmap() will map the wrong address if the offset > > of a chunk is less than its size due to the driver adjusting the address. > > That presumably needs to change. If we want to allow mmap() on a subset of the enclave, yes. I assume it's a simple matter of respecting MAP_FIXED. > Are we entirely missing an API to allocate a naturally aligned VA > range? That's kind of annoying. Yes? > > Eliminating sgx_get_unmapped_area() means userspace is once again on the > > hook for naturally aligning the enclave, which is less than desirable. > > > > Looking back at the original API discussions around a builder process[1], > > we never fleshed out the end-to-end flow. While having a builder process > > *sounds* reasonable, in practice it adds a lot of complexity without > > providing much in the way of added security. E.g. in addition to the > > above mmap() issues, since the order of EADDs affects the enclave > > measurement, the enclave owner would need to communicate the exact steps > > to build the enclave, or the builder would need a priori knowledge of the > > enclave format. > > > > Userspace can still restrict access to /dev/sgx/enclave, e.g. by having a > > daemon that requires additional credentials to obtain a new enclave_fd. > > So AFAICT, the only benefit to having a dedicated builder is that it can > > do its own whitelisting of enclaves, but since we're trending towards > > supporting whitelisting enclaves in the kernel, e.g. via sigstruct, > > whitelisting in userspace purely in userspace also provides marginal value. > > > > TL;DR: Requiring VMA backing to build an enclave seems reasonable and sane. > > This isn't necessarily a problem, but we pretty much have to use > mprotect() then. You lost me there. Who needs to mprotect() what? > Maybe the semantics could just be that mmap() on the SGX device gives > natural alignment, but that there is no actual constraint enforced by > the driver as to whether mmap() happens before or after ECREATE. > After all, it's *ugly* for user code to reserve its address range with > an awkward giant mmap(), there's nothing fundamentally wrong with it. > > As far as I know from this whole discussion, we still haven't come up > with any credible way to avoid tracking, per enclave page, whether > that page came from unmodified PROT_EXEC memory. Disallowing mmap() after ECREATE is credible, but apparently not palatable. :-) But actually, there's no need to disallow mmap() after ECREATE since the LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap() bypassed LSM checks? The real problem is that mmap()'ng an existing enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back at square one. Tracking permissions per enclave page isn't difficult, it's the new SGX specific LSM hooks and mprotect() interactions that I want to avoid. Jumping back to mmap(), AIUI the fundamental issue is that we want to allow building/running an enclave without FILE__WRITE and FILE__EXECUTE, otherwise FILE__WRITE and FILE__EXECUTE become meaningless. Assuming I'm not off in the weeds, that means we really just need to special case mmap() on enclaves so it can map enclave memory using the verified page permissions so as not to run afoul of LSM checks. All other behaviors, e.g. mprotect(), can reuse the existing LSM checks for shared mappings. So, what if we snapshot the permissions for each enclave page at EADD, and then special case mmap() to propagate flags from the snapshot to the VMA? More or less the same idea as doing mprotect_fixup() using the source VMA during EADD. We could define the EADD semantics to match this as well, e.g. only propagate the flags from the source VMA to the enclave VMA if the EADD range is fully mapped with PROT_NONE. This would allow the enclave builder concept, albeit with funky semantics, and wouldn't require new LSM hooks. E.g. something like this: static inline void sgx_mmap_update_prot_flags(struct vm_area_struct *vma, struct sgx_encl *encl) { struct radix_tree_iter iter; struct sgx_encl_page *entry; unsigned long addr; vm_flags_t flags; void **slot; /* * SGX special: if userspace is requesting PROT_NONE and pages have * been added to the enclave, then propagate the flags snapshot from * the enclave to the VMA. Do this if and only if all overlapped * pages are defined and have identical permissions. Stuffing the * VMA on PROT_NONE allows userspace to map EPC pages without being * incorrectly rejected by LSMs due to insufficient permissions (the * snapshottted flags have alaredy been vetted). */ if (vma->vm_flags & (VM_READ|VM_WRITE|VM_EXEC)) return; flags = 0; for (addr = vma->vm_start; addr < vma->vm_end; addr += PAGE_SIZE) { entry = radix_tree_lookup(&encl->page_tree, addr >> PAGE_SHIFT); if (!entry && flags) return; if (!flags && entry) { if (addr == vma->vm_start) { flags = entry->vm_flags; continue; } return; } if (entry && flags && entry->vm_flags != flags) return; } vma->vm_flags |= flags; }
On Wed, May 22, 2019 at 03:42:45PM -0700, Andy Lutomirski wrote: > As far as I know from this whole discussion, we still haven't come up > with any credible way to avoid tracking, per enclave page, whether > that page came from unmodified PROT_EXEC memory. So is this in the context that the enclave is read from another VMA and not through a file descriptor? Is that locked in? /Jarkko
On Thu, May 23, 2019 at 11:10:48AM +0300, Jarkko Sakkinen wrote: > On Wed, May 22, 2019 at 03:42:45PM -0700, Andy Lutomirski wrote: > > As far as I know from this whole discussion, we still haven't come up > > with any credible way to avoid tracking, per enclave page, whether > > that page came from unmodified PROT_EXEC memory. > > So is this in the context that the enclave is read from another VMA > and not through a file descriptor? Is that locked in? No need to answer. Got in page from Sean's response. /Jarkko
On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: > But actually, there's no need to disallow mmap() after ECREATE since the > LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to > mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap() > bypassed LSM checks? The real problem is that mmap()'ng an existing > enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back > at square one. I'm lost with the constraints we want to set. We can still support fork() if we take a step back from v20 and require the mmap(). Given the recent comments, I'd guess that is the best compromise i.e. multiple processes can still share an enclave within the limitations of ancestor hierarchy. Is this the constraint we agree now upon? Some emails are a bit contradicting in this sense. > Tracking permissions per enclave page isn't difficult, it's the new SGX > specific LSM hooks and mprotect() interactions that I want to avoid. > > Jumping back to mmap(), AIUI the fundamental issue is that we want to > allow building/running an enclave without FILE__WRITE and FILE__EXECUTE, > otherwise FILE__WRITE and FILE__EXECUTE become meaningless. Assuming I'm > not off in the weeds, that means we really just need to special case > mmap() on enclaves so it can map enclave memory using the verified page > permissions so as not to run afoul of LSM checks. All other behaviors, > e.g. mprotect(), can reuse the existing LSM checks for shared mappings. > > So, what if we snapshot the permissions for each enclave page at EADD, > and then special case mmap() to propagate flags from the snapshot to the > VMA? More or less the same idea as doing mprotect_fixup() using the > source VMA during EADD. We could define the EADD semantics to match > this as well, e.g. only propagate the flags from the source VMA to the > enclave VMA if the EADD range is fully mapped with PROT_NONE. This would > allow the enclave builder concept, albeit with funky semantics, and > wouldn't require new LSM hooks. Dropped off here completely. What if the mmap() is done before any of the EADD operations? > > E.g. something like this: > > static inline void sgx_mmap_update_prot_flags(struct vm_area_struct *vma, > struct sgx_encl *encl) > { > struct radix_tree_iter iter; > struct sgx_encl_page *entry; > unsigned long addr; > vm_flags_t flags; > void **slot; > > /* > * SGX special: if userspace is requesting PROT_NONE and pages have > * been added to the enclave, then propagate the flags snapshot from > * the enclave to the VMA. Do this if and only if all overlapped > * pages are defined and have identical permissions. Stuffing the > * VMA on PROT_NONE allows userspace to map EPC pages without being > * incorrectly rejected by LSMs due to insufficient permissions (the > * snapshottted flags have alaredy been vetted). > */ > if (vma->vm_flags & (VM_READ|VM_WRITE|VM_EXEC)) > return; > > flags = 0; > > for (addr = vma->vm_start; addr < vma->vm_end; addr += PAGE_SIZE) { > entry = radix_tree_lookup(&encl->page_tree, addr >> PAGE_SHIFT); > > if (!entry && flags) > return; > if (!flags && entry) { > if (addr == vma->vm_start) { > flags = entry->vm_flags; > continue; > } > return; > } > if (entry && flags && entry->vm_flags != flags) > return; > > } > vma->vm_flags |= flags; > } This looks flakky and error prone. You'd better have some "shadow VMAs" and check that you have such matching size of the VMA you try to mmap() and check flags from that. Who would call this function anyhow and when? Would be better to first agree on constraints. I have zero idea within which kind of enviroment this snippet would live e.g. - mmap() (before, after?) - multi process constraint (only fork or full on versatility) /Jarkko
On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: > On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: > > But actually, there's no need to disallow mmap() after ECREATE since the > > LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to > > mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap() > > bypassed LSM checks? The real problem is that mmap()'ng an existing > > enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back > > at square one. > > I'm lost with the constraints we want to set. As is today, SELinux policies would require enclave loaders to have FILE__WRITE and FILE__EXECUTE permissions on /dev/sgx/enclave. Presumably other LSMs have similar requirements. Requiring all processes to have FILE__{WRITE,EXECUTE} permissions means the permissions don't add much value, e.g. they can't be used to distinguish between an enclave that is being loaded from an unmodified file and an enclave that is being generated on the fly, e.g. Graphene. Looking back at Andy's mail, he was talking about requiring FILE__EXECUTE to run an enclave, so perhaps it's only FILE__WRITE that we're trying to special case. > We can still support fork() if we take a step back from v20 and require > the mmap(). Given the recent comments, I'd guess that is the best > compromise i.e. multiple processes can still share an enclave within > the limitations of ancestor hierarchy. Is this the constraint we agree > now upon? Some emails are a bit contradicting in this sense. > > > Tracking permissions per enclave page isn't difficult, it's the new SGX > > specific LSM hooks and mprotect() interactions that I want to avoid. > > > > Jumping back to mmap(), AIUI the fundamental issue is that we want to > > allow building/running an enclave without FILE__WRITE and FILE__EXECUTE, > > otherwise FILE__WRITE and FILE__EXECUTE become meaningless. Assuming I'm > > not off in the weeds, that means we really just need to special case > > mmap() on enclaves so it can map enclave memory using the verified page > > permissions so as not to run afoul of LSM checks. All other behaviors, > > e.g. mprotect(), can reuse the existing LSM checks for shared mappings. > > > > So, what if we snapshot the permissions for each enclave page at EADD, > > and then special case mmap() to propagate flags from the snapshot to the > > VMA? More or less the same idea as doing mprotect_fixup() using the > > source VMA during EADD. We could define the EADD semantics to match > > this as well, e.g. only propagate the flags from the source VMA to the > > enclave VMA if the EADD range is fully mapped with PROT_NONE. This would > > allow the enclave builder concept, albeit with funky semantics, and > > wouldn't require new LSM hooks. > > Dropped off here completely. What if the mmap() is done before any of > the EADD operations? Three options I can think of, in descending order of magic required: 1. Do nothing. Userspace would essentially be required to mmap() the enclave after EINIT, which is ugly but not breaking since userspace could mmap() the enclave with a placeholder VMA prior to building the enclave, and then a series of mmap() to establish its "real" mapping. 2. Propagate the permissions from EADD to the VMAs of the current mm if the entire EADD range is mapped and the mapping is PROT_NONE. 3. Propagate the permissions from EADD to the VMAs of all mm structs that have mapped some piece of the enclave, following the matching rules from #2. > > E.g. something like this: > > > > static inline void sgx_mmap_update_prot_flags(struct vm_area_struct *vma, > > struct sgx_encl *encl) > > { > > struct radix_tree_iter iter; > > struct sgx_encl_page *entry; > > unsigned long addr; > > vm_flags_t flags; > > void **slot; > > > > /* > > * SGX special: if userspace is requesting PROT_NONE and pages have > > * been added to the enclave, then propagate the flags snapshot from > > * the enclave to the VMA. Do this if and only if all overlapped > > * pages are defined and have identical permissions. Stuffing the > > * VMA on PROT_NONE allows userspace to map EPC pages without being > > * incorrectly rejected by LSMs due to insufficient permissions (the > > * snapshottted flags have alaredy been vetted). > > */ > > if (vma->vm_flags & (VM_READ|VM_WRITE|VM_EXEC)) > > return; > > > > flags = 0; > > > > for (addr = vma->vm_start; addr < vma->vm_end; addr += PAGE_SIZE) { > > entry = radix_tree_lookup(&encl->page_tree, addr >> PAGE_SHIFT); > > > > if (!entry && flags) > > return; > > if (!flags && entry) { > > if (addr == vma->vm_start) { > > flags = entry->vm_flags; > > continue; > > } > > return; > > } > > if (entry && flags && entry->vm_flags != flags) > > return; > > > > } > > vma->vm_flags |= flags; > > } > > This looks flakky and error prone. You'd better have some "shadow VMAs" > and check that you have such matching size of the VMA you try to mmap() > and check flags from that. > > Who would call this function anyhow and when? > > Would be better to first agree on constraints. I have zero idea within > which kind of enviroment this snippet would live e.g. > > - mmap() (before, after?) > - multi process constraint (only fork or full on versatility) This would be called from sgx_mmap(), i.e. mmap(). Sorry that wasn't at all clear. The idea is to inherit the protections from the enclave pages if mmap() was passed PROT_NONE, but do so in a paranoid way. I don't think multi-process contraints would be required. This would allow an individual process to inherit the pre-verified protections. Other process(es) could map the enclave page with different protections, but doing so would require the appropriate FILE__* permissions for the other process(es).
On Thu, May 23, 2019 at 7:17 AM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: > > On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: > > > But actually, there's no need to disallow mmap() after ECREATE since the > > > LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to > > > mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap() > > > bypassed LSM checks? The real problem is that mmap()'ng an existing > > > enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back > > > at square one. > > > > I'm lost with the constraints we want to set. > > As is today, SELinux policies would require enclave loaders to have > FILE__WRITE and FILE__EXECUTE permissions on /dev/sgx/enclave. Presumably > other LSMs have similar requirements. Requiring all processes to have > FILE__{WRITE,EXECUTE} permissions means the permissions don't add much > value, e.g. they can't be used to distinguish between an enclave that is > being loaded from an unmodified file and an enclave that is being > generated on the fly, e.g. Graphene. > > Looking back at Andy's mail, he was talking about requiring FILE__EXECUTE > to run an enclave, so perhaps it's only FILE__WRITE that we're trying to > special case. > I thought about this some more, and I have a new proposal that helps address the ELRANGE alignment issue and the permission issue at the cost of some extra verbosity. Maybe you all can poke holes in it :) The basic idea is to make everything more explicit from a user's perspective. Here's how it works: Opening /dev/sgx/enclave gives an enclave_fd that, by design, doesn't give EXECUTE or WRITE. mmap() on the enclave_fd only works if you pass PROT_NONE and gives the correct alignment. The resulting VMA cannot be mprotected or mremapped. It can't be mmapped at all until after ECREATE because the alignment isn't known before that. Associated with the enclave are a bunch (up to 7) "enclave segment inodes". These are anon_inodes that are created automagically. An enclave segment is a group of pages, not necessary contiguous, with an upper bound on the memory permissions. Each enclave page belongs to a segment. When you do EADD, you tell the driver what segment you're adding to. [0] This means that EADD gets an extra argument that is a permission mask for the page -- in addition to the initial SECINFO, you also pass to EADD something to the effect of "I promise never to map this with permissions greater than RX". Then we just need some way to mmap a region from an enclave segment. This could be done by having a way to get an fd for an enclave segment or it could be done by having a new ioctl SGX_IOC_MAP_SEGMENT. User code would use this operation to replace, MAP_FIXED-style, ranges from the big PROT_NONE mapping with the relevant pages from the enclave segment. The resulting vma would only have VM_MAYWRITE if the segment is W, only have VM_MAYEXEC if the segment is X, and only have VM_MAYREAD if the segment is R. Depending on implementation details, the VMAs might need to restrict mremap() to avoid mapping pages that aren't part of the segment in question. It's plausible that this whole thing works without the magic segment inodes under the hood, but figuring that out would need a careful look at how all the core mm bits and LSM bits work together. To get all the LSM stuff to work, SELinux will need some way to automatically assign an appropriate label to the segment inodes. I assume that such a mechanism already exists and gets used for things like sockets, but I haven't actually confirmed this. [0] There needs to be some vaguely intelligent semantics if you EADD the *same* address more than once. A simple solution would be to disallow it if the segments don't match.
On Thu, May 23, 2019 at 07:17:52AM -0700, Sean Christopherson wrote: > On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: > > On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: > > > But actually, there's no need to disallow mmap() after ECREATE since the > > > LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to > > > mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap() > > > bypassed LSM checks? The real problem is that mmap()'ng an existing > > > enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back > > > at square one. > > > > I'm lost with the constraints we want to set. > > As is today, SELinux policies would require enclave loaders to have > FILE__WRITE and FILE__EXECUTE permissions on /dev/sgx/enclave. Presumably > other LSMs have similar requirements. Requiring all processes to have > FILE__{WRITE,EXECUTE} permissions means the permissions don't add much > value, e.g. they can't be used to distinguish between an enclave that is > being loaded from an unmodified file and an enclave that is being > generated on the fly, e.g. Graphene. > > Looking back at Andy's mail, he was talking about requiring FILE__EXECUTE > to run an enclave, so perhaps it's only FILE__WRITE that we're trying to > special case. Argh, as I was working through Andy's latest proposal I realized that I was subconciously making FILE__READ imply FILE__EXECUTE. The idea behind inheriting permissions from the source VMA is to exempt "standard" enclaves from needing FILE__WRITE. But if we don't add an exemption for FILE__EXECUTE as well, then all enclaves need FILE__EXECUTE, which means FILE__EXECUTE can't be used to identify the case where userspace is mapping an inherited PROT_WRITE page as PROT_EXEC. And if the SGX magic exempts FILE__EXECUTE, then FILE__READ implies FILE__EXECUTE. Yuck.
On Thu, May 23, 2019 at 08:38:17AM -0700, Andy Lutomirski wrote: > On Thu, May 23, 2019 at 7:17 AM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: > > > On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: > > > > But actually, there's no need to disallow mmap() after ECREATE since the > > > > LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to > > > > mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap() > > > > bypassed LSM checks? The real problem is that mmap()'ng an existing > > > > enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back > > > > at square one. > > > > > > I'm lost with the constraints we want to set. > > > > As is today, SELinux policies would require enclave loaders to have > > FILE__WRITE and FILE__EXECUTE permissions on /dev/sgx/enclave. Presumably > > other LSMs have similar requirements. Requiring all processes to have > > FILE__{WRITE,EXECUTE} permissions means the permissions don't add much > > value, e.g. they can't be used to distinguish between an enclave that is > > being loaded from an unmodified file and an enclave that is being > > generated on the fly, e.g. Graphene. > > > > Looking back at Andy's mail, he was talking about requiring FILE__EXECUTE > > to run an enclave, so perhaps it's only FILE__WRITE that we're trying to > > special case. > > > > I thought about this some more, and I have a new proposal that helps > address the ELRANGE alignment issue and the permission issue at the > cost of some extra verbosity. Maybe you all can poke holes in it :) > The basic idea is to make everything more explicit from a user's > perspective. Here's how it works: > > Opening /dev/sgx/enclave gives an enclave_fd that, by design, doesn't > give EXECUTE or WRITE. mmap() on the enclave_fd only works if you > pass PROT_NONE and gives the correct alignment. The resulting VMA > cannot be mprotected or mremapped. It can't be mmapped at all until I assume you're thinking of clearing all VM_MAY* flags in sgx_mmap()? > after ECREATE because the alignment isn't known before that. I don't follow. The alignment is known because userspace knows the size of its enclave. The initial unknown is the address, but that becomes known once the initial mmap() completes. > Associated with the enclave are a bunch (up to 7) "enclave segment I assume 7 = R, W, X, RW, RX, WX and RWX? > inodes". These are anon_inodes that are created automagically. An > enclave segment is a group of pages, not necessary contiguous, with an > upper bound on the memory permissions. Each enclave page belongs to a > segment. When you do EADD, you tell the driver what segment you're > adding to. [0] This means that EADD gets an extra argument that is a > permission mask for the page -- in addition to the initial SECINFO, > you also pass to EADD something to the effect of "I promise never to > map this with permissions greater than RX". > > Then we just need some way to mmap a region from an enclave segment. > This could be done by having a way to get an fd for an enclave segment > or it could be done by having a new ioctl SGX_IOC_MAP_SEGMENT. User > code would use this operation to replace, MAP_FIXED-style, ranges from > the big PROT_NONE mapping with the relevant pages from the enclave > segment. The resulting vma would only have VM_MAYWRITE if the segment > is W, only have VM_MAYEXEC if the segment is X, and only have > VM_MAYREAD if the segment is R. Depending on implementation details, > the VMAs might need to restrict mremap() to avoid mapping pages that > aren't part of the segment in question. If my above assumptions regarding VM_MAY* and the "7 segments" are correct, IIUC you're proposing that an LSM could have policies for each of the anon inodes, e.g. grant/deny RWX vs. RW vs RX. Am I in the ballpark? > It's plausible that this whole thing works without the magic segment > inodes under the hood, but figuring that out would need a careful look > at how all the core mm bits and LSM bits work together. > > To get all the LSM stuff to work, SELinux will need some way to > automatically assign an appropriate label to the segment inodes. I > assume that such a mechanism already exists and gets used for things > like sockets, but I haven't actually confirmed this. I (obviously) don't fully understand your proposal, but I don't think we want to hook inodes, e.g. AppArmor doesn't implement inode_permission() but does implement file_mprotect() and mmap_file(), which feel like the natural hooks for this sort of thing. I also think it's overkill, e.g. AppArmor doesn't have a concept of EXECMOD, EXECMEM, EXECHEAP, etc.., so I don't think we need to go beyond detecting W+X scenarios. Starting with your original idea of tracking "safe to execute" and Cedric's of propagating the permissions from the source VMA, but tweaked with your new idea of clearing VM_MAY* and a custom MAP_FIXED/mprotect(). Add SGX_IOC_MPROTECT (or SGX_IOC_MAP_REGION?) that works as follows: 1. Track VM_MAY{READ,WRITE,EXEC} flags for each enclave page. 2. SGX_IOC_ADD_REGION, i.e. EADD, initializes the VM_MAY* flags for each enclave page based on the source VMA. 3. sgx_mmap() only works with PROT_NONE, skips alignment stuff if MAP_FIXED, and clears VM_MAY{READ,WRITE,EXEC}. 4. mprotect() on /dev/sgx/enclave doesn't work because the VMA doesn't have any VM_MAY{READ,WRITE,EXEC} capabilities. 5. Deny mremap() post-ECREATE as the address and size of the enclave are fixed at ECREATE (in hardware). 6. SGX_IOC_MPROTECT works like normal mprotect(), except the VM_MAY* flags are pulled from the enclave pages, and its call to security_file_mprotect() is VM_READ|VM_EXEC by default. The LSM call sets VM_WRITE iff the enclave page has both VM_MAYWRITE and VM_MAYEXEC. The idea here is to require READ and EXECUTE to run an enclave, and only require WRITE on /dev/sgx/enclave when the enclave can execute modified memory. To support SGX2 down the road, which will want to convert a page to executable on the fly, we could add: 7. SGX_IOC_EXTEND_PERMISSIONS enables userspace to extend the VM_MAY* flags for an enclave page, e.g. to make a page executable. SGX_IOC_MPROTECT is still required to actually map the page. Notably, adding a RW page to the enclave, e.g. to grow its heap, doesn't require WRITE, whereas adding a RWX page, e.g. for dynamic loading, would require WRITE. This can only extend! E.g. userspace can't circumvent the WRITE requirement by clearing VM_MAYWRITE. Note, FILE__WRITE on /dev/sgx/enclave is essentially equivalent to FILE__EXECMOD. Using FILE__WRITE in this way means there are no changes to SELinux (triggering FILE__EXECMOD would be awkward), and AppArmor also picks up extra protections for enclaves. > [0] There needs to be some vaguely intelligent semantics if you EADD > the *same* address more than once. A simple solution would be to > disallow it if the segments don't match. I don't see any reason to allow duplicate EADD as it serves no purpose, e.g. doing so changes the enclave's measurement and that's it.
On Thu, May 23, 2019 at 4:40 PM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Thu, May 23, 2019 at 08:38:17AM -0700, Andy Lutomirski wrote: > > On Thu, May 23, 2019 at 7:17 AM Sean Christopherson > > <sean.j.christopherson@intel.com> wrote: > > > > > > On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: > > > > On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: > > > > > But actually, there's no need to disallow mmap() after ECREATE since the > > > > > LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to > > > > > mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap() > > > > > bypassed LSM checks? The real problem is that mmap()'ng an existing > > > > > enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back > > > > > at square one. > > > > > > > > I'm lost with the constraints we want to set. > > > > > > As is today, SELinux policies would require enclave loaders to have > > > FILE__WRITE and FILE__EXECUTE permissions on /dev/sgx/enclave. Presumably > > > other LSMs have similar requirements. Requiring all processes to have > > > FILE__{WRITE,EXECUTE} permissions means the permissions don't add much > > > value, e.g. they can't be used to distinguish between an enclave that is > > > being loaded from an unmodified file and an enclave that is being > > > generated on the fly, e.g. Graphene. > > > > > > Looking back at Andy's mail, he was talking about requiring FILE__EXECUTE > > > to run an enclave, so perhaps it's only FILE__WRITE that we're trying to > > > special case. > > > > > > > I thought about this some more, and I have a new proposal that helps > > address the ELRANGE alignment issue and the permission issue at the > > cost of some extra verbosity. Maybe you all can poke holes in it :) > > The basic idea is to make everything more explicit from a user's > > perspective. Here's how it works: > > > > Opening /dev/sgx/enclave gives an enclave_fd that, by design, doesn't > > give EXECUTE or WRITE. mmap() on the enclave_fd only works if you > > pass PROT_NONE and gives the correct alignment. The resulting VMA > > cannot be mprotected or mremapped. It can't be mmapped at all until > > I assume you're thinking of clearing all VM_MAY* flags in sgx_mmap()? > > > after ECREATE because the alignment isn't known before that. > > I don't follow. The alignment is known because userspace knows the size > of its enclave. The initial unknown is the address, but that becomes > known once the initial mmap() completes. [...] I think I made the mistake of getting too carried away with implementation details rather than just getting to the point. And I misremembered the ECREATE flow -- oops. Let me try again. First, here are some problems with some earlier proposals (mine, yours Cedric's): - Having the EADD operation always work but have different effects depending on the source memory permissions is, at the very least, confusing. - If we want to encourage user programs to be well-behaved, we want to make it easy to map the RX parts of an enclave RX, the RW parts RW, the RO parts R, etc. But this interacts poorly with the sgx_mmap() alignment magic, as you've pointed out. - We don't want to couple LSMs with SGX too tightly. So here's how a nice interface might work: int enclave_fd = open("/dev/sgx/enclave", O_RDWR); /* enclave_fd points to a totally blank enclave. Before ECREATE, we need to decide on an address. */ void *addr = mmap(NULL, size, PROT_NONE, MAP_SHARED, enclave_fd, 0); /* we have an address! */ ioctl(enclave_fd, ECREATE, ...); /* now add some data to the enclave. We want the RWX addition to fail immediately unless we have the relevant LSM pemission. Similarly, we want the RX addition to fail immediately unless the source VMA is appropriate. */ ioctl(enclave_fd, EADD, rx_source_1, MAXPERM=RX, ...); [the ... includes SECINFO, which the kernel doesn't really care about] ioctl(enclave_fd, EADD, ro_source_1, MAXPERM=RX ...); ioctl(enclave_fd, EADD, rw_source_1, MAXPERM=RW ...); ioctl(enclave_fd, EADD, rwx_source_1, MAXPERM=RWX ...); ioctl(enclave_fd, EINIT, ...); /* presumably pass sigstruct_fd here, too. */ /* at this point, all is well except that the enclave is mapped PROT_NONE. There are a couple ways I can imagine to fix this. */ We could use mmap: mmap(baseaddr+offset, len, PROT_READ, MAP_SHARED | MAP_FIXED, enclave_fd, 0); /* only succeeds if MAXPERM & R == R */ But this has some annoying implications with regard to sgx_get_unmapped_area(). We could use an ioctl: ioctl(enclave_fd, SGX_IOC_MPROTECT, offset, len, PROT_READ); which has the potentially nice property that we can completely bypass the LSM hooks, because the LSM has *already* vetted everything when the EADD calls were allowed. Or we could maybe even just use mprotect() itself: mprotect(baseaddr + offset, len, PROT_READ); Or, for the really evil option, we could use a bit of magic in .fault and do nothing here. Instead we'd make the initial mapping PROT_READ|PROT_WRITE|PROT_EXEC and have .fault actually instantiate the PTEs with the intersection of the VMA permissions and MAXPERM. I don't think I like this alternative, since it feels more magical than needed and it will be harder to debug. I like the fact that /proc/self/maps shows the actual permissions in all the other variants. All of the rest of the crud in my earlier email was just implementation details. The point I was trying to make was that I think it's possible to implement this without making too much of a mess internally. I think I favor the mprotect() approach since it makes the behavior fairly obvious. I don't think any of this needs to change for SGX2. We'd have an ioctl() that does EAUG and specifies MAXPERM. Trying to mprotect() a page that hasn't been added yet with any permission other than PROT_NONE would fail. I suppose we might end up needing a way to let the EAUG operation *change* MAXPERM, and this operation would have to do some more LSM checks and walk all the existing mappings to make sure they're consistent with the new MAXPERM. As an aside, I wonder if Linus et all would be okay with a new MAP_FULLY_ALIGNED mmap() flag that allocated memory aligned to the requested size. Then we could get rid of yet another bit of magic. --Andy
Hi Andy, > From: Andy Lutomirski [mailto:luto@kernel.org] > Sent: Thursday, May 23, 2019 6:18 PM > > On Thu, May 23, 2019 at 4:40 PM Sean Christopherson <sean.j.christopherson@intel.com> > wrote: > > > > On Thu, May 23, 2019 at 08:38:17AM -0700, Andy Lutomirski wrote: > > > On Thu, May 23, 2019 at 7:17 AM Sean Christopherson > > > <sean.j.christopherson@intel.com> wrote: > > > > > > > > On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: > > > > > On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: > > > > > > But actually, there's no need to disallow mmap() after ECREATE > > > > > > since the LSM checks also apply to mmap(), e.g. FILE__EXECUTE > > > > > > would be needed to > > > > > > mmap() any enclave pages PROT_EXEC. I guess my past self > > > > > > thought mmap() bypassed LSM checks? The real problem is that > > > > > > mmap()'ng an existing enclave would require FILE__WRITE and > > > > > > FILE__EXECUTE, which puts us back at square one. > > > > > > > > > > I'm lost with the constraints we want to set. > > > > > > > > As is today, SELinux policies would require enclave loaders to > > > > have FILE__WRITE and FILE__EXECUTE permissions on > > > > /dev/sgx/enclave. Presumably other LSMs have similar > > > > requirements. Requiring all processes to have > > > > FILE__{WRITE,EXECUTE} permissions means the permissions don't add > > > > much value, e.g. they can't be used to distinguish between an > > > > enclave that is being loaded from an unmodified file and an enclave that is being > generated on the fly, e.g. Graphene. > > > > > > > > Looking back at Andy's mail, he was talking about requiring > > > > FILE__EXECUTE to run an enclave, so perhaps it's only FILE__WRITE > > > > that we're trying to special case. > > > > > > > > > > I thought about this some more, and I have a new proposal that helps > > > address the ELRANGE alignment issue and the permission issue at the > > > cost of some extra verbosity. Maybe you all can poke holes in it :) > > > The basic idea is to make everything more explicit from a user's > > > perspective. Here's how it works: > > > > > > Opening /dev/sgx/enclave gives an enclave_fd that, by design, > > > doesn't give EXECUTE or WRITE. mmap() on the enclave_fd only works > > > if you pass PROT_NONE and gives the correct alignment. The > > > resulting VMA cannot be mprotected or mremapped. It can't be > > > mmapped at all until > > > > I assume you're thinking of clearing all VM_MAY* flags in sgx_mmap()? > > > > > after ECREATE because the alignment isn't known before that. > > > > I don't follow. The alignment is known because userspace knows the > > size of its enclave. The initial unknown is the address, but that > > becomes known once the initial mmap() completes. > > [...] > > I think I made the mistake of getting too carried away with implementation details rather > than just getting to the point. And I misremembered the ECREATE flow -- oops. Let me try > again. First, here are some problems with some earlier proposals (mine, yours > Cedric's): > > - Having the EADD operation always work but have different effects depending on the > source memory permissions is, at the very least, confusing. Inheriting permissions from source pages IMHO is the easiest way to validate the EPC permissions without any changes to LSM. And the argument about its security is also easy to make. I understand that it may take some effort to document it properly but otherwise don't see any practical issues with it. > > - If we want to encourage user programs to be well-behaved, we want to make it easy to > map the RX parts of an enclave RX, the RW parts RW, the RO parts R, etc. But this > interacts poorly with the sgx_mmap() alignment magic, as you've pointed out. > > - We don't want to couple LSMs with SGX too tightly. > > So here's how a nice interface might work: > > int enclave_fd = open("/dev/sgx/enclave", O_RDWR); > > /* enclave_fd points to a totally blank enclave. Before ECREATE, we need to decide on an > address. */ > > void *addr = mmap(NULL, size, PROT_NONE, MAP_SHARED, enclave_fd, 0); > > /* we have an address! */ > > ioctl(enclave_fd, ECREATE, ...); > > /* now add some data to the enclave. We want the RWX addition to fail > immediately unless we have the relevant LSM pemission. Similarly, we > want the RX addition to fail immediately unless the source VMA is appropriate. */ > > ioctl(enclave_fd, EADD, rx_source_1, MAXPERM=RX, ...); [the ... > includes SECINFO, which the kernel doesn't really care about] ioctl(enclave_fd, EADD, > ro_source_1, MAXPERM=RX ...); ioctl(enclave_fd, EADD, rw_source_1, MAXPERM=RW ...); > ioctl(enclave_fd, EADD, rwx_source_1, MAXPERM=RWX ...); If MAXPERM is taken from ioctl parameters, the real question here is how to validate MAXPERM. Guess we shouldn't allow arbitrary MAXPERM to be specified by user code, and the only logical source I can think of is from the source pages (or from the enclave source file, but memory mapping is preferred because it offers more flexibility). > > ioctl(enclave_fd, EINIT, ...); /* presumably pass sigstruct_fd here, too. */ > > /* at this point, all is well except that the enclave is mapped PROT_NONE. There are a > couple ways I can imagine to fix this. */ > > We could use mmap: > > mmap(baseaddr+offset, len, PROT_READ, MAP_SHARED | MAP_FIXED, enclave_fd, 0); /* only > succeeds if MAXPERM & R == R */ > > But this has some annoying implications with regard to sgx_get_unmapped_area(). We could > use an ioctl: There's an easy fix. Just let sgx_get_unmapped_area() do the natural alignment only if MAP_FIXED is *not* set, otherwise, honor both address and len. But mmap() is subject to LSM check (probably against /dev/sgx/enclave?). How to do mmap(RX) if FILE__EXECUTE is *not* granted for /dev/sgx/enclave, even if MAXPERM=RX? > > ioctl(enclave_fd, SGX_IOC_MPROTECT, offset, len, PROT_READ); > > which has the potentially nice property that we can completely bypass the LSM hooks, > because the LSM has *already* vetted everything when the EADD calls were allowed. Or we > could maybe even just use > mprotect() itself: > > mprotect(baseaddr + offset, len, PROT_READ); How to bypass LSM hooks in this mprotect()? > > Or, for the really evil option, we could use a bit of magic in .fault and do nothing here. > Instead we'd make the initial mapping PROT_READ|PROT_WRITE|PROT_EXEC and have .fault > actually instantiate the PTEs with the intersection of the VMA permissions and MAXPERM. I > don't think I like this alternative, since it feels more magical than needed and it will > be harder to debug. I like the fact that /proc/self/maps shows the actual permissions in > all the other variants. Agreed. > > > All of the rest of the crud in my earlier email was just implementation details. The > point I was trying to make was that I think it's possible to implement this without making > too much of a mess internally. I think I favor the mprotect() approach since it makes the > behavior fairly obvious. > > I don't think any of this needs to change for SGX2. We'd have an > ioctl() that does EAUG and specifies MAXPERM. Trying to mprotect() a page that hasn't > been added yet with any permission other than PROT_NONE would fail. I suppose we might > end up needing a way to let the EAUG operation *change* MAXPERM, and this operation would > have to do some more LSM checks and walk all the existing mappings to make sure they're > consistent with the new MAXPERM. EAUG ioctl could be a solution, but isn't optimal at least. What we've done is #PF based. Specifically, an SGX2 enclave will have its heap mapped as RW, but without any pages populated before EINIT. Then when the enclave needs a new page in its heap, it issues EACCEPT, which will cause a #PF and the driver will respond by EAUG a new EPC page. And then the enclave will be resumed and the faulted EACCEPT will be retried (and succeed). > > As an aside, I wonder if Linus et all would be okay with a new MAP_FULLY_ALIGNED mmap() > flag that allocated memory aligned to the requested size. Then we could get rid of yet > another bit of magic. > > --Andy I've also got a chance to think more about it lately. When we talk about EPC page permissions with SGX2 in mind, I think we should distinguish between initial permissions and runtime permissions. Initial permissions refer to the page permissions set at EADD. They are technically set by "untrusted" code so should go by policies similar to those applicable to regular shared objects. Runtime permissions refer to the permissions granted by EMODPE, EAUG and EACCEPTCOPY. They are resulted from inherent behavior of the enclave, which in theory is determined by the enclave's measurements (MRENCLAVE and/or MRSIGNER). And we have 2 distinct files to work with - the enclave file and /dev/sgx/enclave. And I consider the enclave file a logical source for initial permissions while /dev/sgx/enclave is a means to control runtime permissions. Then we can have a simpler approach like the pseudo code below. /** * Summary: * - The enclave file resembles a shared object that contains RO/RX/RW segments * - FILE__* are assigned to /dev/sgx/enclave, to determine acceptable permissions to mmap()/mprotect(), valid combinations are * + FILE__READ - Allow SGX1 enclaves only * + FILE__READ|FILE__WRITE - Allow SGX2 enclaves to expand data segments (e.g. heaps, stacks, etc.) * + FILE__READ|FILE__WRITE|FILE__EXECUTE - Allow SGX2 enclaves to expend both data and code segments. This is necessary to support dynamically linked enclaves (e.g. Graphene) * + FILE__READ|FILE__EXECUTE - Allow RW->RX changes for SGX1 enclaves - necessary to support dynamically linked enclaves (e.g. Graphene) on SGX1. EXECMEM is also required for this to work * + <None> - Disallow the calling process to launch any enclaves */ /* Step 1: mmap() the enclave file according to the segment attributes (similar to what dlopen() would do for regular shared objects) */ int image_fd = open("/path/to/enclave/file", O_RDONLY); foreach phdr in loadable segments /* phdr->p_type == PT_LOAD */ { /* <segment permission> below is subject to LSM checks */ loadable_segments[i] = mmap(NULL, phdr->p_memsz, MAP_PRIATE, <segment permission>, image_fd, phdr->p_offset); } /* Step 2: Create enclave */ int enclave_fd = open("/dev/sgx/enclave", O_RDONLY /* or O_RDWR for SGX2 enclaves */); void *enclave_base = mmap(NULL, <enclave size>, MAP_SHARED, PROT_READ, enclave_fd, 0); /* Only FILE__READ is required here */ ioctl(enclave_fd, IOC_ECREATE, ...); /* Step 3: EADD and map initial EPC pages */ foreach s in loadable_segments { /* IOC_EADD_AND_MAP_SEGMENT will make sure s->perm is a subset of VMA permissions of the source pages, and use that as *both* EPCM and VMA permissions). * Given enclave_fd may have FILE__READ only, LSM has to be bypassed so the "mmap" part has to be done inside the driver. * Initial EPC pages will be mapped only once, so no inode is needed to remember the initial permissions. mmap/mprotect afterwards are subject to FILE__* on /dev/sgx/enclave * The key point here is: permissions of source pages govern initial permissions of EADD'ed pages, regardless FILE__* on /dev/sgx/enclave */ ioctl(enclave_fd, IOC_EADD_AND_MAP_SEGMENT, s->base, s->size, s->perm...); } /* EADD other enclave components, e.g. TCS, stacks, heaps, etc. */ ioctl(enclave_fd, IOC_EADD_AND_MAP_SEGMENT, tcs, 0x1000, RW | PT_TCS...); ioctl(enclave_fd, IOC_EADD_AND_MAP_SEGMENT, <zero page>, <stack size>, RW...); ... /* Step 4 (SGX2 only): Reserve ranges for additional heaps, stacks, etc. */ /* FILE__WRITE required to allow expansion of data segments at runtime */ /* Key point here is: permissions, if needed to change at runtime, are subject to FILL__* on /dev/sgx/enclave */ mprotect(<heap address>, <heap size>, PROT_READ | PROT_WRITE); /* Step 5: EINIT */ ioctl(IOC_EINIT, <sigstruct>...); /* Step 6 (SGX2 only): Set RX for dynamically loaded code pages (e.g. Graphene, encrypted enclaves, etc.) as needed, at runtime */ /* FILE__EXECUTE required */ mprotect(<RX address>, <RX size>, PROT_READ | PROT_EXEC); -Cedric
On 5/23/19 11:38 AM, Andy Lutomirski wrote: > On Thu, May 23, 2019 at 7:17 AM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: >> >> On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: >>> On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: >>>> But actually, there's no need to disallow mmap() after ECREATE since the >>>> LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to >>>> mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap() >>>> bypassed LSM checks? The real problem is that mmap()'ng an existing >>>> enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back >>>> at square one. >>> >>> I'm lost with the constraints we want to set. >> >> As is today, SELinux policies would require enclave loaders to have >> FILE__WRITE and FILE__EXECUTE permissions on /dev/sgx/enclave. Presumably >> other LSMs have similar requirements. Requiring all processes to have >> FILE__{WRITE,EXECUTE} permissions means the permissions don't add much >> value, e.g. they can't be used to distinguish between an enclave that is >> being loaded from an unmodified file and an enclave that is being >> generated on the fly, e.g. Graphene. >> >> Looking back at Andy's mail, he was talking about requiring FILE__EXECUTE >> to run an enclave, so perhaps it's only FILE__WRITE that we're trying to >> special case. >> > > I thought about this some more, and I have a new proposal that helps > address the ELRANGE alignment issue and the permission issue at the > cost of some extra verbosity. Maybe you all can poke holes in it :) > The basic idea is to make everything more explicit from a user's > perspective. Here's how it works: > > Opening /dev/sgx/enclave gives an enclave_fd that, by design, doesn't > give EXECUTE or WRITE. mmap() on the enclave_fd only works if you > pass PROT_NONE and gives the correct alignment. The resulting VMA > cannot be mprotected or mremapped. It can't be mmapped at all until > after ECREATE because the alignment isn't known before that. > > Associated with the enclave are a bunch (up to 7) "enclave segment > inodes". These are anon_inodes that are created automagically. An > enclave segment is a group of pages, not necessary contiguous, with an > upper bound on the memory permissions. Each enclave page belongs to a > segment. When you do EADD, you tell the driver what segment you're > adding to. [0] This means that EADD gets an extra argument that is a > permission mask for the page -- in addition to the initial SECINFO, > you also pass to EADD something to the effect of "I promise never to > map this with permissions greater than RX". > > Then we just need some way to mmap a region from an enclave segment. > This could be done by having a way to get an fd for an enclave segment > or it could be done by having a new ioctl SGX_IOC_MAP_SEGMENT. User > code would use this operation to replace, MAP_FIXED-style, ranges from > the big PROT_NONE mapping with the relevant pages from the enclave > segment. The resulting vma would only have VM_MAYWRITE if the segment > is W, only have VM_MAYEXEC if the segment is X, and only have > VM_MAYREAD if the segment is R. Depending on implementation details, > the VMAs might need to restrict mremap() to avoid mapping pages that > aren't part of the segment in question. > > It's plausible that this whole thing works without the magic segment > inodes under the hood, but figuring that out would need a careful look > at how all the core mm bits and LSM bits work together. > > To get all the LSM stuff to work, SELinux will need some way to > automatically assign an appropriate label to the segment inodes. I > assume that such a mechanism already exists and gets used for things > like sockets, but I haven't actually confirmed this. I don't follow that. socket inodes are not anon inodes, and anon inodes have no per-instance data by definition, and typically you're only dealing with a single anon inode for all files, and hence they were long ago marked S_PRIVATE and exempted from all LSM checking except for EXECMEM on mmap/mprotect PROT_EXEC. We have no way to perform useful security checking on them currently. socket inodes we can label from their creating process but even that's not going to support multiple labels for different sockets created by the same process unless the process explicitly used setsockcreatecon(3) aka /proc/self/attr/sockcreate > > [0] There needs to be some vaguely intelligent semantics if you EADD > the *same* address more than once. A simple solution would be to > disallow it if the segments don't match. >
On 5/24/19 3:24 AM, Xing, Cedric wrote: > Hi Andy, > >> From: Andy Lutomirski [mailto:luto@kernel.org] >> Sent: Thursday, May 23, 2019 6:18 PM >> >> On Thu, May 23, 2019 at 4:40 PM Sean Christopherson <sean.j.christopherson@intel.com> >> wrote: >>> >>> On Thu, May 23, 2019 at 08:38:17AM -0700, Andy Lutomirski wrote: >>>> On Thu, May 23, 2019 at 7:17 AM Sean Christopherson >>>> <sean.j.christopherson@intel.com> wrote: >>>>> >>>>> On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: >>>>>> On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: >>>>>>> But actually, there's no need to disallow mmap() after ECREATE >>>>>>> since the LSM checks also apply to mmap(), e.g. FILE__EXECUTE >>>>>>> would be needed to >>>>>>> mmap() any enclave pages PROT_EXEC. I guess my past self >>>>>>> thought mmap() bypassed LSM checks? The real problem is that >>>>>>> mmap()'ng an existing enclave would require FILE__WRITE and >>>>>>> FILE__EXECUTE, which puts us back at square one. >>>>>> >>>>>> I'm lost with the constraints we want to set. >>>>> >>>>> As is today, SELinux policies would require enclave loaders to >>>>> have FILE__WRITE and FILE__EXECUTE permissions on >>>>> /dev/sgx/enclave. Presumably other LSMs have similar >>>>> requirements. Requiring all processes to have >>>>> FILE__{WRITE,EXECUTE} permissions means the permissions don't add >>>>> much value, e.g. they can't be used to distinguish between an >>>>> enclave that is being loaded from an unmodified file and an enclave that is being >> generated on the fly, e.g. Graphene. >>>>> >>>>> Looking back at Andy's mail, he was talking about requiring >>>>> FILE__EXECUTE to run an enclave, so perhaps it's only FILE__WRITE >>>>> that we're trying to special case. >>>>> >>>> >>>> I thought about this some more, and I have a new proposal that helps >>>> address the ELRANGE alignment issue and the permission issue at the >>>> cost of some extra verbosity. Maybe you all can poke holes in it :) >>>> The basic idea is to make everything more explicit from a user's >>>> perspective. Here's how it works: >>>> >>>> Opening /dev/sgx/enclave gives an enclave_fd that, by design, >>>> doesn't give EXECUTE or WRITE. mmap() on the enclave_fd only works >>>> if you pass PROT_NONE and gives the correct alignment. The >>>> resulting VMA cannot be mprotected or mremapped. It can't be >>>> mmapped at all until >>> >>> I assume you're thinking of clearing all VM_MAY* flags in sgx_mmap()? >>> >>>> after ECREATE because the alignment isn't known before that. >>> >>> I don't follow. The alignment is known because userspace knows the >>> size of its enclave. The initial unknown is the address, but that >>> becomes known once the initial mmap() completes. >> >> [...] >> >> I think I made the mistake of getting too carried away with implementation details rather >> than just getting to the point. And I misremembered the ECREATE flow -- oops. Let me try >> again. First, here are some problems with some earlier proposals (mine, yours >> Cedric's): >> >> - Having the EADD operation always work but have different effects depending on the >> source memory permissions is, at the very least, confusing. > > Inheriting permissions from source pages IMHO is the easiest way to validate the EPC permissions without any changes to LSM. And the argument about its security is also easy to make. > > I understand that it may take some effort to document it properly but otherwise don't see any practical issues with it. > >> >> - If we want to encourage user programs to be well-behaved, we want to make it easy to >> map the RX parts of an enclave RX, the RW parts RW, the RO parts R, etc. But this >> interacts poorly with the sgx_mmap() alignment magic, as you've pointed out. >> >> - We don't want to couple LSMs with SGX too tightly. >> >> So here's how a nice interface might work: >> >> int enclave_fd = open("/dev/sgx/enclave", O_RDWR); >> >> /* enclave_fd points to a totally blank enclave. Before ECREATE, we need to decide on an >> address. */ >> >> void *addr = mmap(NULL, size, PROT_NONE, MAP_SHARED, enclave_fd, 0); >> >> /* we have an address! */ >> >> ioctl(enclave_fd, ECREATE, ...); >> >> /* now add some data to the enclave. We want the RWX addition to fail >> immediately unless we have the relevant LSM pemission. Similarly, we >> want the RX addition to fail immediately unless the source VMA is appropriate. */ >> >> ioctl(enclave_fd, EADD, rx_source_1, MAXPERM=RX, ...); [the ... >> includes SECINFO, which the kernel doesn't really care about] ioctl(enclave_fd, EADD, >> ro_source_1, MAXPERM=RX ...); ioctl(enclave_fd, EADD, rw_source_1, MAXPERM=RW ...); >> ioctl(enclave_fd, EADD, rwx_source_1, MAXPERM=RWX ...); > > If MAXPERM is taken from ioctl parameters, the real question here is how to validate MAXPERM. Guess we shouldn't allow arbitrary MAXPERM to be specified by user code, and the only logical source I can think of is from the source pages (or from the enclave source file, but memory mapping is preferred because it offers more flexibility). > >> >> ioctl(enclave_fd, EINIT, ...); /* presumably pass sigstruct_fd here, too. */ >> >> /* at this point, all is well except that the enclave is mapped PROT_NONE. There are a >> couple ways I can imagine to fix this. */ >> >> We could use mmap: >> >> mmap(baseaddr+offset, len, PROT_READ, MAP_SHARED | MAP_FIXED, enclave_fd, 0); /* only >> succeeds if MAXPERM & R == R */ >> >> But this has some annoying implications with regard to sgx_get_unmapped_area(). We could >> use an ioctl: > > There's an easy fix. Just let sgx_get_unmapped_area() do the natural alignment only if MAP_FIXED is *not* set, otherwise, honor both address and len. > > But mmap() is subject to LSM check (probably against /dev/sgx/enclave?). How to do mmap(RX) if FILE__EXECUTE is *not* granted for /dev/sgx/enclave, even if MAXPERM=RX? > >> >> ioctl(enclave_fd, SGX_IOC_MPROTECT, offset, len, PROT_READ); >> >> which has the potentially nice property that we can completely bypass the LSM hooks, >> because the LSM has *already* vetted everything when the EADD calls were allowed. Or we >> could maybe even just use >> mprotect() itself: >> >> mprotect(baseaddr + offset, len, PROT_READ); > > How to bypass LSM hooks in this mprotect()? > >> >> Or, for the really evil option, we could use a bit of magic in .fault and do nothing here. >> Instead we'd make the initial mapping PROT_READ|PROT_WRITE|PROT_EXEC and have .fault >> actually instantiate the PTEs with the intersection of the VMA permissions and MAXPERM. I >> don't think I like this alternative, since it feels more magical than needed and it will >> be harder to debug. I like the fact that /proc/self/maps shows the actual permissions in >> all the other variants. > > Agreed. > >> >> >> All of the rest of the crud in my earlier email was just implementation details. The >> point I was trying to make was that I think it's possible to implement this without making >> too much of a mess internally. I think I favor the mprotect() approach since it makes the >> behavior fairly obvious. >> >> I don't think any of this needs to change for SGX2. We'd have an >> ioctl() that does EAUG and specifies MAXPERM. Trying to mprotect() a page that hasn't >> been added yet with any permission other than PROT_NONE would fail. I suppose we might >> end up needing a way to let the EAUG operation *change* MAXPERM, and this operation would >> have to do some more LSM checks and walk all the existing mappings to make sure they're >> consistent with the new MAXPERM. > > EAUG ioctl could be a solution, but isn't optimal at least. What we've done is #PF based. Specifically, an SGX2 enclave will have its heap mapped as RW, but without any pages populated before EINIT. Then when the enclave needs a new page in its heap, it issues EACCEPT, which will cause a #PF and the driver will respond by EAUG a new EPC page. And then the enclave will be resumed and the faulted EACCEPT will be retried (and succeed). > >> >> As an aside, I wonder if Linus et all would be okay with a new MAP_FULLY_ALIGNED mmap() >> flag that allocated memory aligned to the requested size. Then we could get rid of yet >> another bit of magic. >> >> --Andy > > I've also got a chance to think more about it lately. > > When we talk about EPC page permissions with SGX2 in mind, I think we should distinguish between initial permissions and runtime permissions. Initial permissions refer to the page permissions set at EADD. They are technically set by "untrusted" code so should go by policies similar to those applicable to regular shared objects. Runtime permissions refer to the permissions granted by EMODPE, EAUG and EACCEPTCOPY. They are resulted from inherent behavior of the enclave, which in theory is determined by the enclave's measurements (MRENCLAVE and/or MRSIGNER). > > And we have 2 distinct files to work with - the enclave file and /dev/sgx/enclave. And I consider the enclave file a logical source for initial permissions while /dev/sgx/enclave is a means to control runtime permissions. Then we can have a simpler approach like the pseudo code below. > > /** > * Summary: > * - The enclave file resembles a shared object that contains RO/RX/RW segments > * - FILE__* are assigned to /dev/sgx/enclave, to determine acceptable permissions to mmap()/mprotect(), valid combinations are > * + FILE__READ - Allow SGX1 enclaves only > * + FILE__READ|FILE__WRITE - Allow SGX2 enclaves to expand data segments (e.g. heaps, stacks, etc.) > * + FILE__READ|FILE__WRITE|FILE__EXECUTE - Allow SGX2 enclaves to expend both data and code segments. This is necessary to support dynamically linked enclaves (e.g. Graphene) > * + FILE__READ|FILE__EXECUTE - Allow RW->RX changes for SGX1 enclaves - necessary to support dynamically linked enclaves (e.g. Graphene) on SGX1. EXECMEM is also required for this to work I think EXECMOD would fit better than EXECMEM for this case; the former is applied for RW->RX changes for private file mappings while the latter is applied for WX private file mappings. > * + <None> - Disallow the calling process to launch any enclaves > */ > > /* Step 1: mmap() the enclave file according to the segment attributes (similar to what dlopen() would do for regular shared objects) */ > int image_fd = open("/path/to/enclave/file", O_RDONLY); FILE__READ checked to enclave file upon open(). > foreach phdr in loadable segments /* phdr->p_type == PT_LOAD */ { > /* <segment permission> below is subject to LSM checks */ > loadable_segments[i] = mmap(NULL, phdr->p_memsz, MAP_PRIATE, <segment permission>, image_fd, phdr->p_offset); FILE__READ revalidated and FILE__EXECUTE checked to enclave file upon mmap() for PROT_READ and PROT_EXEC respectively. FILE__WRITE not checked even for PROT_WRITE mappings since it is a private file mapping and writes do not reach the file. EXECMEM checked if any segment permission has both W and X simultaneously. EXECMOD checked on any subsequent mprotect() RW->RX changes (if modified). > } > > /* Step 2: Create enclave */ > int enclave_fd = open("/dev/sgx/enclave", O_RDONLY /* or O_RDWR for SGX2 enclaves */); FILE__READ checked (SGX1) or both FILE__READ and FILE__WRITE checked (SGX2) to /dev/sgx/enclave upon open(). Assuming that we are returning an open file referencing the /dev/sgx/enclave inode and not an anon inode, else we lose all subsequent FILE__* checking on mmap/mprotect and trigger EXECMEM on any mmap/mprotect PROT_EXEC. > void *enclave_base = mmap(NULL, <enclave size>, MAP_SHARED, PROT_READ, enclave_fd, 0); /* Only FILE__READ is required here */ FILE__READ revalidated to /dev/sgx/enclave upon mmap(). > ioctl(enclave_fd, IOC_ECREATE, ...); > > /* Step 3: EADD and map initial EPC pages */ > foreach s in loadable_segments { > /* IOC_EADD_AND_MAP_SEGMENT will make sure s->perm is a subset of VMA permissions of the source pages, and use that as *both* EPCM and VMA permissions). > * Given enclave_fd may have FILE__READ only, LSM has to be bypassed so the "mmap" part has to be done inside the driver. > * Initial EPC pages will be mapped only once, so no inode is needed to remember the initial permissions. mmap/mprotect afterwards are subject to FILE__* on /dev/sgx/enclave > * The key point here is: permissions of source pages govern initial permissions of EADD'ed pages, regardless FILE__* on /dev/sgx/enclave > */ > ioctl(enclave_fd, IOC_EADD_AND_MAP_SEGMENT, s->base, s->size, s->perm...); > } > /* EADD other enclave components, e.g. TCS, stacks, heaps, etc. */ > ioctl(enclave_fd, IOC_EADD_AND_MAP_SEGMENT, tcs, 0x1000, RW | PT_TCS...); > ioctl(enclave_fd, IOC_EADD_AND_MAP_SEGMENT, <zero page>, <stack size>, RW...); > ... > > /* Step 4 (SGX2 only): Reserve ranges for additional heaps, stacks, etc. */ > /* FILE__WRITE required to allow expansion of data segments at runtime */ > /* Key point here is: permissions, if needed to change at runtime, are subject to FILL__* on /dev/sgx/enclave */ > mprotect(<heap address>, <heap size>, PROT_READ | PROT_WRITE); FILE__READ and FILE__WRITE revalidated to /dev/sgx/enclave upon mprotect(). > > /* Step 5: EINIT */ > ioctl(IOC_EINIT, <sigstruct>...); > > /* Step 6 (SGX2 only): Set RX for dynamically loaded code pages (e.g. Graphene, encrypted enclaves, etc.) as needed, at runtime */ > /* FILE__EXECUTE required */ > mprotect(<RX address>, <RX size>, PROT_READ | PROT_EXEC); FILE__READ revalidated and FILE__EXECUTE checked to /dev/sgx/enclave upon mprotect(). Cumulative set of checks at this point is FILE__READ|FILE__WRITE|FILE__EXECUTE. What would the step be for a SGX1 RW->RX change? How would that trigger EXECMOD? Do we really need to distinguish it from the SGX2 dynamically loaded code case? > > -Cedric >
On Fri, May 24, 2019 at 12:24 AM Xing, Cedric <cedric.xing@intel.com> wrote: > > Hi Andy, > > > From: Andy Lutomirski [mailto:luto@kernel.org] > > Sent: Thursday, May 23, 2019 6:18 PM > > > > On Thu, May 23, 2019 at 4:40 PM Sean Christopherson <sean.j.christopherson@intel.com> > > wrote: > > > > > > On Thu, May 23, 2019 at 08:38:17AM -0700, Andy Lutomirski wrote: > > > > On Thu, May 23, 2019 at 7:17 AM Sean Christopherson > > > > <sean.j.christopherson@intel.com> wrote: > > > > > > > > > > On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: > > > > > > On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: > > > > > > > But actually, there's no need to disallow mmap() after ECREATE > > > > > > > since the LSM checks also apply to mmap(), e.g. FILE__EXECUTE > > > > > > > would be needed to > > > > > > > mmap() any enclave pages PROT_EXEC. I guess my past self > > > > > > > thought mmap() bypassed LSM checks? The real problem is that > > > > > > > mmap()'ng an existing enclave would require FILE__WRITE and > > > > > > > FILE__EXECUTE, which puts us back at square one. > > > > > > > > > > > > I'm lost with the constraints we want to set. > > > > > > > > > > As is today, SELinux policies would require enclave loaders to > > > > > have FILE__WRITE and FILE__EXECUTE permissions on > > > > > /dev/sgx/enclave. Presumably other LSMs have similar > > > > > requirements. Requiring all processes to have > > > > > FILE__{WRITE,EXECUTE} permissions means the permissions don't add > > > > > much value, e.g. they can't be used to distinguish between an > > > > > enclave that is being loaded from an unmodified file and an enclave that is being > > generated on the fly, e.g. Graphene. > > > > > > > > > > Looking back at Andy's mail, he was talking about requiring > > > > > FILE__EXECUTE to run an enclave, so perhaps it's only FILE__WRITE > > > > > that we're trying to special case. > > > > > > > > > > > > > I thought about this some more, and I have a new proposal that helps > > > > address the ELRANGE alignment issue and the permission issue at the > > > > cost of some extra verbosity. Maybe you all can poke holes in it :) > > > > The basic idea is to make everything more explicit from a user's > > > > perspective. Here's how it works: > > > > > > > > Opening /dev/sgx/enclave gives an enclave_fd that, by design, > > > > doesn't give EXECUTE or WRITE. mmap() on the enclave_fd only works > > > > if you pass PROT_NONE and gives the correct alignment. The > > > > resulting VMA cannot be mprotected or mremapped. It can't be > > > > mmapped at all until > > > > > > I assume you're thinking of clearing all VM_MAY* flags in sgx_mmap()? > > > > > > > after ECREATE because the alignment isn't known before that. > > > > > > I don't follow. The alignment is known because userspace knows the > > > size of its enclave. The initial unknown is the address, but that > > > becomes known once the initial mmap() completes. > > > > [...] > > > > I think I made the mistake of getting too carried away with implementation details rather > > than just getting to the point. And I misremembered the ECREATE flow -- oops. Let me try > > again. First, here are some problems with some earlier proposals (mine, yours > > Cedric's): > > > > - Having the EADD operation always work but have different effects depending on the > > source memory permissions is, at the very least, confusing. > > Inheriting permissions from source pages IMHO is the easiest way to validate the EPC permissions without any changes to LSM. And the argument about its security is also easy to make. > > I understand that it may take some effort to document it properly but otherwise don't see any practical issues with it. My objection is to the fact that it's implicit. I have no problem with some operation succeeding if the source address is X and failing if it's !X, but I don't think it's fantastic to have it succeed in either case but do different things. For what it's worth, while this is a bit of a theoretical issue for X, but I think it's a real problem with W. To avoid accidentally mapping an enclave page X and then later mapping the same page W (potentially in a different VMA), I think it will be a lot simpler if the driver can track which pages are allowed to ever be W. We definitely *don't* want an interface in which the eventual writability of a page is inferred from the W permission on the source address, since we do *not* want to force anyone to map their enclave file PROT_WRITE or even to open it O_RDWR. With the explicit MAXPERM passed in, this issue goes away. You can specify W if you want W. > > > > > - If we want to encourage user programs to be well-behaved, we want to make it easy to > > map the RX parts of an enclave RX, the RW parts RW, the RO parts R, etc. But this > > interacts poorly with the sgx_mmap() alignment magic, as you've pointed out. > > > > - We don't want to couple LSMs with SGX too tightly. > > > > So here's how a nice interface might work: > > > > int enclave_fd = open("/dev/sgx/enclave", O_RDWR); > > > > /* enclave_fd points to a totally blank enclave. Before ECREATE, we need to decide on an > > address. */ > > > > void *addr = mmap(NULL, size, PROT_NONE, MAP_SHARED, enclave_fd, 0); > > > > /* we have an address! */ > > > > ioctl(enclave_fd, ECREATE, ...); > > > > /* now add some data to the enclave. We want the RWX addition to fail > > immediately unless we have the relevant LSM pemission. Similarly, we > > want the RX addition to fail immediately unless the source VMA is appropriate. */ > > > > ioctl(enclave_fd, EADD, rx_source_1, MAXPERM=RX, ...); [the ... > > includes SECINFO, which the kernel doesn't really care about] ioctl(enclave_fd, EADD, > > ro_source_1, MAXPERM=RX ...); ioctl(enclave_fd, EADD, rw_source_1, MAXPERM=RW ...); > > ioctl(enclave_fd, EADD, rwx_source_1, MAXPERM=RWX ...); > > If MAXPERM is taken from ioctl parameters, the real question here is how to validate MAXPERM. Guess we shouldn't allow arbitrary MAXPERM to be specified by user code, and the only logical source I can think of is from the source pages (or from the enclave source file, but memory mapping is preferred because it offers more flexibility). That's exactly what I intended here. If you specify MAXPERM=RX, then the kernel can validate that the source address is executable. > > > > > ioctl(enclave_fd, EINIT, ...); /* presumably pass sigstruct_fd here, too. */ > > > > /* at this point, all is well except that the enclave is mapped PROT_NONE. There are a > > couple ways I can imagine to fix this. */ > > > > We could use mmap: > > > > mmap(baseaddr+offset, len, PROT_READ, MAP_SHARED | MAP_FIXED, enclave_fd, 0); /* only > > succeeds if MAXPERM & R == R */ > > > > But this has some annoying implications with regard to sgx_get_unmapped_area(). We could > > use an ioctl: > > There's an easy fix. Just let sgx_get_unmapped_area() do the natural alignment only if MAP_FIXED is *not* set, otherwise, honor both address and len. > > But mmap() is subject to LSM check (probably against /dev/sgx/enclave?). How to do mmap(RX) if FILE__EXECUTE is *not* granted for /dev/sgx/enclave, even if MAXPERM=RX? I think we just let /dev/sgx/enclave be FILE__EXECUTE. We don't *have* to make it so that FILE__WRITE and FILE__EXECUTE on /dev/sgx/enclave means you can create RWX enclave mappings. > > > > > ioctl(enclave_fd, SGX_IOC_MPROTECT, offset, len, PROT_READ); > > > > which has the potentially nice property that we can completely bypass the LSM hooks, > > because the LSM has *already* vetted everything when the EADD calls were allowed. Or we > > could maybe even just use > > mprotect() itself: > > > > mprotect(baseaddr + offset, len, PROT_READ); > > How to bypass LSM hooks in this mprotect()? Hmm. I guess we either use FILE__WRITE and FILE__EXECUTE or we use ioctl(). > > > > > Or, for the really evil option, we could use a bit of magic in .fault and do nothing here. > > Instead we'd make the initial mapping PROT_READ|PROT_WRITE|PROT_EXEC and have .fault > > actually instantiate the PTEs with the intersection of the VMA permissions and MAXPERM. I > > don't think I like this alternative, since it feels more magical than needed and it will > > be harder to debug. I like the fact that /proc/self/maps shows the actual permissions in > > all the other variants. > > Agreed. > > > > > > > All of the rest of the crud in my earlier email was just implementation details. The > > point I was trying to make was that I think it's possible to implement this without making > > too much of a mess internally. I think I favor the mprotect() approach since it makes the > > behavior fairly obvious. > > > > I don't think any of this needs to change for SGX2. We'd have an > > ioctl() that does EAUG and specifies MAXPERM. Trying to mprotect() a page that hasn't > > been added yet with any permission other than PROT_NONE would fail. I suppose we might > > end up needing a way to let the EAUG operation *change* MAXPERM, and this operation would > > have to do some more LSM checks and walk all the existing mappings to make sure they're > > consistent with the new MAXPERM. > > EAUG ioctl could be a solution, but isn't optimal at least. What we've done is #PF based. Specifically, an SGX2 enclave will have its heap mapped as RW, but without any pages populated before EINIT. Then when the enclave needs a new page in its heap, it issues EACCEPT, which will cause a #PF and the driver will respond by EAUG a new EPC page. And then the enclave will be resumed and the faulted EACCEPT will be retried (and succeed). > If the driver works like that, then whatever call sets up this lazily allocated heap could do the MAXPERM part. That being said, is the performance advantage from putting this logic in the kernel instead of in the untrusted part of the SDK really worthwhile? > > > > As an aside, I wonder if Linus et all would be okay with a new MAP_FULLY_ALIGNED mmap() > > flag that allocated memory aligned to the requested size. Then we could get rid of yet > > another bit of magic. > > > > --Andy > > I've also got a chance to think more about it lately. > > When we talk about EPC page permissions with SGX2 in mind, I think we should distinguish between initial permissions and runtime permissions. Initial permissions refer to the page permissions set at EADD. They are technically set by "untrusted" code so should go by policies similar to those applicable to regular shared objects. Runtime permissions refer to the permissions granted by EMODPE, EAUG and EACCEPTCOPY. They are resulted from inherent behavior of the enclave, which in theory is determined by the enclave's measurements (MRENCLAVE and/or MRSIGNER). > > And we have 2 distinct files to work with - the enclave file and /dev/sgx/enclave. And I consider the enclave file a logical source for initial permissions while /dev/sgx/enclave is a means to control runtime permissions. Then we can have a simpler approach like the pseudo code below. > > /** > * Summary: > * - The enclave file resembles a shared object that contains RO/RX/RW segments > * - FILE__* are assigned to /dev/sgx/enclave, to determine acceptable permissions to mmap()/mprotect(), valid combinations are > * + FILE__READ - Allow SGX1 enclaves only > * + FILE__READ|FILE__WRITE - Allow SGX2 enclaves to expand data segments (e.g. heaps, stacks, etc.) I think this is a non-starter :( FILE__WRITE also means that you can write to the file, and the admin / policy author will almost never want to allow that.
Hi Stephen, > On 5/24/19 3:24 AM, Xing, Cedric wrote: > > > > When we talk about EPC page permissions with SGX2 in mind, I think we > should distinguish between initial permissions and runtime permissions. > Initial permissions refer to the page permissions set at EADD. They are > technically set by "untrusted" code so should go by policies similar to > those applicable to regular shared objects. Runtime permissions refer to > the permissions granted by EMODPE, EAUG and EACCEPTCOPY. They are > resulted from inherent behavior of the enclave, which in theory is > determined by the enclave's measurements (MRENCLAVE and/or MRSIGNER). > > > > And we have 2 distinct files to work with - the enclave file and > /dev/sgx/enclave. And I consider the enclave file a logical source for > initial permissions while /dev/sgx/enclave is a means to control runtime > permissions. Then we can have a simpler approach like the pseudo code > below. > > > > /** > > * Summary: > > * - The enclave file resembles a shared object that contains > RO/RX/RW segments > > * - FILE__* are assigned to /dev/sgx/enclave, to determine > acceptable permissions to mmap()/mprotect(), valid combinations are > > * + FILE__READ - Allow SGX1 enclaves only > > * + FILE__READ|FILE__WRITE - Allow SGX2 enclaves to expand data > segments (e.g. heaps, stacks, etc.) > > * + FILE__READ|FILE__WRITE|FILE__EXECUTE - Allow SGX2 enclaves to > expend both data and code segments. This is necessary to support > dynamically linked enclaves (e.g. Graphene) > > * + FILE__READ|FILE__EXECUTE - Allow RW->RX changes for SGX1 > enclaves - necessary to support dynamically linked enclaves (e.g. > Graphene) on SGX1. EXECMEM is also required for this to work > > I think EXECMOD would fit better than EXECMEM for this case; the former > is applied for RW->RX changes for private file mappings while the latter > is applied for WX private file mappings. > > > * + <None> - Disallow the calling process to launch any enclaves > > */ > > > > /* Step 1: mmap() the enclave file according to the segment attributes > > (similar to what dlopen() would do for regular shared objects) */ int > > image_fd = open("/path/to/enclave/file", O_RDONLY); > > FILE__READ checked to enclave file upon open(). Yes. We'd like to have the enclave file pass LSM/IMA checks and let EPC pages "inherit" the permissions from it as "initial" permissions. > > > foreach phdr in loadable segments /* phdr->p_type == PT_LOAD */ { > > /* <segment permission> below is subject to LSM checks */ > > loadable_segments[i] = mmap(NULL, phdr->p_memsz, MAP_PRIATE, > > <segment permission>, image_fd, phdr->p_offset); > > FILE__READ revalidated and FILE__EXECUTE checked to enclave file upon > mmap() for PROT_READ and PROT_EXEC respectively. FILE__WRITE not > checked even for PROT_WRITE mappings since it is a private file mapping > and writes do not reach the file. EXECMEM checked if any segment > permission has both W and X simultaneously. EXECMOD checked on any > subsequent mprotect() RW->RX changes (if modified). Yes. The intention here is to make sure all X pages come directly from file (unless EXECMEM or EXECMOD is granted). And because the driver will grant X only if the source page also has X, we can assert that all executable EPC pages are loaded from a file that has passed LSM/IMA checks. > > > } > > > > /* Step 2: Create enclave */ > > int enclave_fd = open("/dev/sgx/enclave", O_RDONLY /* or O_RDWR for > > SGX2 enclaves */); > > FILE__READ checked (SGX1) or both FILE__READ and FILE__WRITE checked > (SGX2) to /dev/sgx/enclave upon open(). Assuming that we are returning > an open file referencing the /dev/sgx/enclave inode and not an anon > inode, else we lose all subsequent FILE__* checking on mmap/mprotect and > trigger EXECMEM on any mmap/mprotect PROT_EXEC. Yes, the returned fd will be referencing /dev/sgx/enclave. The intention here is to limit EPC "runtime" permissions by the permissions granted to /dev/sgx/enclave, in order to allow user/administrator to specify what kinds of enclaves a given process can launch. Per your earlier comments, FILE__EXECMOD is probably also needed to support dynamically linked enclaves (that require RW->RX changes). > > > void *enclave_base = mmap(NULL, <enclave size>, MAP_SHARED, PROT_READ, > > enclave_fd, 0); /* Only FILE__READ is required here */ > > FILE__READ revalidated to /dev/sgx/enclave upon mmap(). Yes. This mmap() is to set "default" permissions for regions that do *not* have EPC pages populated. It is significant only for SGX2, to specify what action to take by the SGX driver upon #PF with those regions. For example, a R attempt (usually triggered by EACCEPT) within a RW region will cause SGX driver to EAUG a page at the fault address. > > > ioctl(enclave_fd, IOC_ECREATE, ...); > > > > /* Step 3: EADD and map initial EPC pages */ foreach s in > > loadable_segments { > > /* IOC_EADD_AND_MAP_SEGMENT will make sure s->perm is a subset of > VMA permissions of the source pages, and use that as *both* EPCM and VMA > permissions). > > * Given enclave_fd may have FILE__READ only, LSM has to be > bypassed so the "mmap" part has to be done inside the driver. > > * Initial EPC pages will be mapped only once, so no inode is > needed to remember the initial permissions. mmap/mprotect afterwards are > subject to FILE__* on /dev/sgx/enclave > > * The key point here is: permissions of source pages govern > initial permissions of EADD'ed pages, regardless FILE__* on > /dev/sgx/enclave > > */ > > ioctl(enclave_fd, IOC_EADD_AND_MAP_SEGMENT, s->base, s->size, > > s->perm...); } > > /* EADD other enclave components, e.g. TCS, stacks, heaps, etc. */ > > ioctl(enclave_fd, IOC_EADD_AND_MAP_SEGMENT, tcs, 0x1000, RW | > > PT_TCS...); ioctl(enclave_fd, IOC_EADD_AND_MAP_SEGMENT, <zero page>, > > <stack size>, RW...); ... > > > > /* Step 4 (SGX2 only): Reserve ranges for additional heaps, stacks, > > etc. */ > > /* FILE__WRITE required to allow expansion of data segments at runtime > > */ > > /* Key point here is: permissions, if needed to change at runtime, are > > subject to FILL__* on /dev/sgx/enclave */ mprotect(<heap address>, > > <heap size>, PROT_READ | PROT_WRITE); > > FILE__READ and FILE__WRITE revalidated to /dev/sgx/enclave upon > mprotect(). Yes. The intention here is to limit "runtime" permissions by accesses granted to the calling process to /dev/sgx/enclave. The "initial" permissions are set by ioctl to bypass LSM, because they are derived/determined by the enclave file. Alternatively, the driver can remember "initial" permissions for each EPC page at IOC_EADD, to be committed at IOC_EINIT. Then this new IOC_EADD_AND_MAP will not be needed. > > > > > /* Step 5: EINIT */ > > ioctl(IOC_EINIT, <sigstruct>...); > > > > /* Step 6 (SGX2 only): Set RX for dynamically loaded code pages (e.g. > > Graphene, encrypted enclaves, etc.) as needed, at runtime */ > > /* FILE__EXECUTE required */ > > mprotect(<RX address>, <RX size>, PROT_READ | PROT_EXEC); > > FILE__READ revalidated and FILE__EXECUTE checked to /dev/sgx/enclave > upon mprotect(). Cumulative set of checks at this point is > FILE__READ|FILE__WRITE|FILE__EXECUTE. > > What would the step be for a SGX1 RW->RX change? How would that trigger > EXECMOD? Do we really need to distinguish it from the SGX2 dynamically > loaded code case? Per your earlier comments, FILE__EXECMOD is also needed I think to allow RW->RX changes. FILE__WRITE controls EAUG. I'm not judging its necessity, but just saying they are both valid combinations. To minimize impact to LSM, I don't want to special-case /dev/sgx/enclave. And the current semantics of FILE__* distinguish those two naturally. BTW, there are usages, such as encrypted enclaves (https://github.com/intel/linux-sgx-pcl), requiring RW->RX but not EAUG. Graphene could also run on SGX1, provided that pages needed by shared objects are all pre-allocated before EINIT. All those could run without FILE__WRITE. > > > > > -Cedric > >
On Fri, May 24, 2019 at 09:43:27AM -0700, Andy Lutomirski wrote: > On Fri, May 24, 2019 at 12:24 AM Xing, Cedric <cedric.xing@intel.com> wrote: > > /** > > * Summary: > > * - The enclave file resembles a shared object that contains RO/RX/RW segments > > * - FILE__* are assigned to /dev/sgx/enclave, to determine acceptable permissions to mmap()/mprotect(), valid combinations are > > * + FILE__READ - Allow SGX1 enclaves only > > * + FILE__READ|FILE__WRITE - Allow SGX2 enclaves to expand data segments (e.g. heaps, stacks, etc.) > > I think this is a non-starter :( FILE__WRITE also means that you can > write to the file, and the admin / policy author will almost never > want to allow that. Why would FILE__WRITE on /dev/sgx/enclave be a problem? An actual write to /dev/sgx/enclave would yield -EINVAL, no?
On Fri, May 24, 2019 at 11:41:29AM -0400, Stephen Smalley wrote: > On 5/24/19 3:24 AM, Xing, Cedric wrote: > >/** > > * Summary: > > * - The enclave file resembles a shared object that contains RO/RX/RW segments > > * - FILE__* are assigned to /dev/sgx/enclave, to determine acceptable permissions to mmap()/mprotect(), valid combinations are > > * + FILE__READ - Allow SGX1 enclaves only > > * + FILE__READ|FILE__WRITE - Allow SGX2 enclaves to expand data segments (e.g. heaps, stacks, etc.) > > * + FILE__READ|FILE__WRITE|FILE__EXECUTE - Allow SGX2 enclaves to expend both data and code segments. This is necessary to support dynamically linked enclaves (e.g. Graphene) > > * + FILE__READ|FILE__EXECUTE - Allow RW->RX changes for SGX1 enclaves - necessary to support dynamically linked enclaves (e.g. Graphene) on SGX1. EXECMEM is also required for this to work > > I think EXECMOD would fit better than EXECMEM for this case; the former is > applied for RW->RX changes for private file mappings while the latter is > applied for WX private file mappings. > > > * + <None> - Disallow the calling process to launch any enclaves > > */ > > > >/* Step 1: mmap() the enclave file according to the segment attributes (similar to what dlopen() would do for regular shared objects) */ > >int image_fd = open("/path/to/enclave/file", O_RDONLY); > > FILE__READ checked to enclave file upon open(). > > >foreach phdr in loadable segments /* phdr->p_type == PT_LOAD */ { > > /* <segment permission> below is subject to LSM checks */ > > loadable_segments[i] = mmap(NULL, phdr->p_memsz, MAP_PRIATE, <segment permission>, image_fd, phdr->p_offset); > > FILE__READ revalidated and FILE__EXECUTE checked to enclave file upon mmap() > for PROT_READ and PROT_EXEC respectively. FILE__WRITE not checked even for > PROT_WRITE mappings since it is a private file mapping and writes do not > reach the file. EXECMEM checked if any segment permission has both W and X > simultaneously. EXECMOD checked on any subsequent mprotect() RW->RX changes > (if modified). Hmm, I've been thinking more about pulling permissions from the source page. Conceptually I'm not sure we need to meet the same requirements as non-enclave DSOs while the enclave is being built, i.e. do we really need to force userspace to fully map the enclave in normal memory? Consider the Graphene scenario where it's building an enclave on the fly. Pulling permissions from the source VMAs means Graphene has to map the code pages of the enclave with X. This means Graphene will need EXEDMOD (or EXECMEM if Graphene isn't careful). In a non-SGX scenario this makes perfect sense since there is no way to verify the end result of RW->RX. But for SGX, assuming enclaves are whitelisted by their sigstruct (checked at EINIT) and because page permissions affect sigstruct.MRENCLAVE, it *is* possible to verify the resulting RX contents. E.g. for the purposes of LSMs, can't we use the .sigstruct file as a proxy for the enclave and require FILE__EXECUTE on the .sigstruct inode to map/run the enclave? Stephen, is my logic sound? If so... - Require FILE__READ+FILE__EXECUTE on .sigstruct to mmap() the enclave. - Prevent userspace from mapping the enclave with permissions beyond the original permissions of the enclave. This can be done by populating VM_MAY{READ,WRITE,EXEC} from the SECINFO (same basic concept as Andy's proposals). E.g. pre-EINIT, mmap() and mprotect() can only succeed with PROT_NONE. - Require FILE__{READ,WRITE,EXECUTE} on /dev/sgx/enclave for simplicity, or provide an alternate SGX_IOC_MPROTECT if we want to sidestep the FILE__WRITE requirement. No changes are required to LSMs, SGX1 has a single LSM touchpoint in its mmap(), and I *think* the only required userspace change is to mmap() PROT_NONE when allocating the enclave's virtual address range. As for Graphene, it doesn't need extra permissions to run its enclaves, it just needs a way to install .sigstruct, which is a generic permissions problem and not SGX specific. For SGX2 maybe: - No additional requirements to map an EAUG'd page as RW page. Not aligned with standard MAP_SHARED behavior, but we really don't want to require FILE__WRITE, and thus allow writes to .sigstruct. - Require FILE__EXECMOD on the .sigstruct to map previously writable page as executable (which indirectly includes all EAUG'd pages). Wiring this up will be a little funky, but we again we don't want to require FILE__WRITE on .sigstruct.
> On May 24, 2019, at 10:07 AM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > >> On Fri, May 24, 2019 at 09:43:27AM -0700, Andy Lutomirski wrote: >>> On Fri, May 24, 2019 at 12:24 AM Xing, Cedric <cedric.xing@intel.com> wrote: >>> /** >>> * Summary: >>> * - The enclave file resembles a shared object that contains RO/RX/RW segments >>> * - FILE__* are assigned to /dev/sgx/enclave, to determine acceptable permissions to mmap()/mprotect(), valid combinations are >>> * + FILE__READ - Allow SGX1 enclaves only >>> * + FILE__READ|FILE__WRITE - Allow SGX2 enclaves to expand data segments (e.g. heaps, stacks, etc.) >> >> I think this is a non-starter :( FILE__WRITE also means that you can >> write to the file, and the admin / policy author will almost never >> want to allow that. > > Why would FILE__WRITE on /dev/sgx/enclave be a problem? An actual > write to /dev/sgx/enclave would yield -EINVAL, no? Bah, read it wrong — FILE__WRITE on the enclave file on disk is no good.
> On May 24, 2019, at 10:42 AM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > >> On Fri, May 24, 2019 at 11:41:29AM -0400, Stephen Smalley wrote: >>> On 5/24/19 3:24 AM, Xing, Cedric wrote: >>> /** >>> * Summary: >>> * - The enclave file resembles a shared object that contains RO/RX/RW segments >>> * - FILE__* are assigned to /dev/sgx/enclave, to determine acceptable permissions to mmap()/mprotect(), valid combinations are >>> * + FILE__READ - Allow SGX1 enclaves only >>> * + FILE__READ|FILE__WRITE - Allow SGX2 enclaves to expand data segments (e.g. heaps, stacks, etc.) >>> * + FILE__READ|FILE__WRITE|FILE__EXECUTE - Allow SGX2 enclaves to expend both data and code segments. This is necessary to support dynamically linked enclaves (e.g. Graphene) >>> * + FILE__READ|FILE__EXECUTE - Allow RW->RX changes for SGX1 enclaves - necessary to support dynamically linked enclaves (e.g. Graphene) on SGX1. EXECMEM is also required for this to work >> >> I think EXECMOD would fit better than EXECMEM for this case; the former is >> applied for RW->RX changes for private file mappings while the latter is >> applied for WX private file mappings. >> >>> * + <None> - Disallow the calling process to launch any enclaves >>> */ >>> >>> /* Step 1: mmap() the enclave file according to the segment attributes (similar to what dlopen() would do for regular shared objects) */ >>> int image_fd = open("/path/to/enclave/file", O_RDONLY); >> >> FILE__READ checked to enclave file upon open(). >> >>> foreach phdr in loadable segments /* phdr->p_type == PT_LOAD */ { >>> /* <segment permission> below is subject to LSM checks */ >>> loadable_segments[i] = mmap(NULL, phdr->p_memsz, MAP_PRIATE, <segment permission>, image_fd, phdr->p_offset); >> >> FILE__READ revalidated and FILE__EXECUTE checked to enclave file upon mmap() >> for PROT_READ and PROT_EXEC respectively. FILE__WRITE not checked even for >> PROT_WRITE mappings since it is a private file mapping and writes do not >> reach the file. EXECMEM checked if any segment permission has both W and X >> simultaneously. EXECMOD checked on any subsequent mprotect() RW->RX changes >> (if modified). > > Hmm, I've been thinking more about pulling permissions from the source > page. Conceptually I'm not sure we need to meet the same requirements as > non-enclave DSOs while the enclave is being built, i.e. do we really need > to force userspace to fully map the enclave in normal memory? > > Consider the Graphene scenario where it's building an enclave on the fly. > Pulling permissions from the source VMAs means Graphene has to map the > code pages of the enclave with X. This means Graphene will need EXEDMOD > (or EXECMEM if Graphene isn't careful). In a non-SGX scenario this makes > perfect sense since there is no way to verify the end result of RW->RX. > > But for SGX, assuming enclaves are whitelisted by their sigstruct (checked > at EINIT) and because page permissions affect sigstruct.MRENCLAVE, it *is* > possible to verify the resulting RX contents. E.g. for the purposes of > LSMs, can't we use the .sigstruct file as a proxy for the enclave and > require FILE__EXECUTE on the .sigstruct inode to map/run the enclave? I think it’s sound for some but not all use cases. I would imagine that a lot of users won’t restrict sigstruct at all — the “use this as a sigstruct” permission will be granted to everything and maybe even to memfd. But even users like that might want to force their enclaves to be hardened such that writable pages are never executable, in which case Graphene may need an exception to run. But maybe I’m nuts.
On Fri, May 24, 2019 at 10:42:43AM -0700, Sean Christopherson wrote: > Hmm, I've been thinking more about pulling permissions from the source > page. Conceptually I'm not sure we need to meet the same requirements as > non-enclave DSOs while the enclave is being built, i.e. do we really need > to force userspace to fully map the enclave in normal memory? > > Consider the Graphene scenario where it's building an enclave on the fly. > Pulling permissions from the source VMAs means Graphene has to map the > code pages of the enclave with X. This means Graphene will need EXEDMOD > (or EXECMEM if Graphene isn't careful). In a non-SGX scenario this makes > perfect sense since there is no way to verify the end result of RW->RX. > > But for SGX, assuming enclaves are whitelisted by their sigstruct (checked > at EINIT) and because page permissions affect sigstruct.MRENCLAVE, it *is* > possible to verify the resulting RX contents. E.g. for the purposes of > LSMs, can't we use the .sigstruct file as a proxy for the enclave and > require FILE__EXECUTE on the .sigstruct inode to map/run the enclave? > > Stephen, is my logic sound? > > > If so... > > - Require FILE__READ+FILE__EXECUTE on .sigstruct to mmap() the enclave. > > - Prevent userspace from mapping the enclave with permissions beyond the > original permissions of the enclave. This can be done by populating > VM_MAY{READ,WRITE,EXEC} from the SECINFO (same basic concept as Andy's > proposals). E.g. pre-EINIT, mmap() and mprotect() can only succeed > with PROT_NONE. > > - Require FILE__{READ,WRITE,EXECUTE} on /dev/sgx/enclave for simplicity, > or provide an alternate SGX_IOC_MPROTECT if we want to sidestep the > FILE__WRITE requirement. One more thought. EADD (and the equivalent SGX2 flow) could do security_mmap_file() with a NULL file on the SECINFO permissions, which would trigger PROCESS_EXECMEM if an enclave attempts to map a page RWX. > No changes are required to LSMs, SGX1 has a single LSM touchpoint in its > mmap(), and I *think* the only required userspace change is to mmap() > PROT_NONE when allocating the enclave's virtual address range. > > As for Graphene, it doesn't need extra permissions to run its enclaves, > it just needs a way to install .sigstruct, which is a generic permissions > problem and not SGX specific. > > > For SGX2 maybe: > > - No additional requirements to map an EAUG'd page as RW page. Not > aligned with standard MAP_SHARED behavior, but we really don't want > to require FILE__WRITE, and thus allow writes to .sigstruct. > > - Require FILE__EXECMOD on the .sigstruct to map previously writable > page as executable (which indirectly includes all EAUG'd pages). > Wiring this up will be a little funky, but we again we don't want > to require FILE__WRITE on .sigstruct. >
On Fri, May 24, 2019 at 10:54:34AM -0700, Andy Lutomirski wrote: > > > On May 24, 2019, at 10:42 AM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > > > Hmm, I've been thinking more about pulling permissions from the source > > page. Conceptually I'm not sure we need to meet the same requirements as > > non-enclave DSOs while the enclave is being built, i.e. do we really need > > to force userspace to fully map the enclave in normal memory? > > > > Consider the Graphene scenario where it's building an enclave on the fly. > > Pulling permissions from the source VMAs means Graphene has to map the > > code pages of the enclave with X. This means Graphene will need EXEDMOD > > (or EXECMEM if Graphene isn't careful). In a non-SGX scenario this makes > > perfect sense since there is no way to verify the end result of RW->RX. > > > > But for SGX, assuming enclaves are whitelisted by their sigstruct (checked > > at EINIT) and because page permissions affect sigstruct.MRENCLAVE, it *is* > > possible to verify the resulting RX contents. E.g. for the purposes of > > LSMs, can't we use the .sigstruct file as a proxy for the enclave and > > require FILE__EXECUTE on the .sigstruct inode to map/run the enclave? > > I think it’s sound for some but not all use cases. I would imagine that a lot > of users won’t restrict sigstruct at all — the “use this as a sigstruct” > permission will be granted to everything and maybe even to memfd. But even > users like that might want to force their enclaves to be hardened such that > writable pages are never executable, in which case Graphene may need an > exception to run. Heh, I belatedly had the same thought. See my follow-up about EXECMEM. > But maybe I’m nuts.
> From: linux-sgx-owner@vger.kernel.org [mailto:linux-sgx- > owner@vger.kernel.org] On Behalf Of Sean Christopherson > Sent: Friday, May 24, 2019 10:55 AM > > On Fri, May 24, 2019 at 10:42:43AM -0700, Sean Christopherson wrote: > > Hmm, I've been thinking more about pulling permissions from the source > > page. Conceptually I'm not sure we need to meet the same requirements > as > > non-enclave DSOs while the enclave is being built, i.e. do we really > need > > to force userspace to fully map the enclave in normal memory? > > > > Consider the Graphene scenario where it's building an enclave on the > fly. > > Pulling permissions from the source VMAs means Graphene has to map the > > code pages of the enclave with X. This means Graphene will need > EXEDMOD > > (or EXECMEM if Graphene isn't careful). In a non-SGX scenario this > makes > > perfect sense since there is no way to verify the end result of RW->RX. > > > > But for SGX, assuming enclaves are whitelisted by their sigstruct > (checked > > at EINIT) and because page permissions affect sigstruct.MRENCLAVE, it > *is* > > possible to verify the resulting RX contents. E.g. for the purposes > of > > LSMs, can't we use the .sigstruct file as a proxy for the enclave and > > require FILE__EXECUTE on the .sigstruct inode to map/run the enclave? > > > > Stephen, is my logic sound? > > > > > > If so... > > > > - Require FILE__READ+FILE__EXECUTE on .sigstruct to mmap() the > enclave. > > > > - Prevent userspace from mapping the enclave with permissions beyond > the > > original permissions of the enclave. This can be done by > populating > > VM_MAY{READ,WRITE,EXEC} from the SECINFO (same basic concept as > Andy's > > proposals). E.g. pre-EINIT, mmap() and mprotect() can only > succeed > > with PROT_NONE. > > > > - Require FILE__{READ,WRITE,EXECUTE} on /dev/sgx/enclave for > simplicity, > > or provide an alternate SGX_IOC_MPROTECT if we want to sidestep > the > > FILE__WRITE requirement. > > One more thought. EADD (and the equivalent SGX2 flow) could do > security_mmap_file() with a NULL file on the SECINFO permissions, which > would trigger PROCESS_EXECMEM if an enclave attempts to map a page RWX. If "initial permissions" for enclaves are less restrictive than shared objects, then it'd become a backdoor for circumventing LSM when enclave whitelisting is *not* in place. For example, an adversary may load a page, which would otherwise never be executable, as an executable page in EPC. In the case a RWX page is needed, the calling process has to have a RWX page serving as the source for EADD so PROCESS__EXECMEM will have been checked. For SGX2, changing an EPC page to RWX is subject to FILE__EXECMEM on /dev/sgx/enclave, which I see as a security benefit because it only affects the enclave but not the whole process hosting it. > > > No changes are required to LSMs, SGX1 has a single LSM touchpoint in > its > > mmap(), and I *think* the only required userspace change is to mmap() > > PROT_NONE when allocating the enclave's virtual address range. I'm not sure I understand the motivation behind this proposal to decouple initial EPC permissions from source pages. I don't think it a big deal to fully mmap() enclave files, which have to be parsed by user mode anyway to determine various things including but not limited to the size of heap(s), size and number of TCSs/stacks/TLS areas, and the overall enclave size. So with PHDRs parsed, it's trivial to mmap() each segment with permissions from its PHDR. > > > > As for Graphene, it doesn't need extra permissions to run its enclaves, > > it just needs a way to install .sigstruct, which is a generic > permissions > > problem and not SGX specific. > > > > > > For SGX2 maybe: > > > > - No additional requirements to map an EAUG'd page as RW page. Not > > aligned with standard MAP_SHARED behavior, but we really don't > want > > to require FILE__WRITE, and thus allow writes to .sigstruct. > > > > - Require FILE__EXECMOD on the .sigstruct to map previously writable > > page as executable (which indirectly includes all EAUG'd pages). > > Wiring this up will be a little funky, but we again we don't want > > to require FILE__WRITE on .sigstruct. > > I'm lost. Why is EAUG tied to permissions on .sigstruct? -Cedric
On Fri, May 24, 2019 at 11:34:32AM -0700, Xing, Cedric wrote: > > From: linux-sgx-owner@vger.kernel.org [mailto:linux-sgx- > > owner@vger.kernel.org] On Behalf Of Sean Christopherson > > Sent: Friday, May 24, 2019 10:55 AM > > > > On Fri, May 24, 2019 at 10:42:43AM -0700, Sean Christopherson wrote: > > > Hmm, I've been thinking more about pulling permissions from the source > > > page. Conceptually I'm not sure we need to meet the same requirements as > > > non-enclave DSOs while the enclave is being built, i.e. do we really need > > > to force userspace to fully map the enclave in normal memory? > > > > > > Consider the Graphene scenario where it's building an enclave on the fly. > > > Pulling permissions from the source VMAs means Graphene has to map the > > > code pages of the enclave with X. This means Graphene will need EXEDMOD > > > (or EXECMEM if Graphene isn't careful). In a non-SGX scenario this makes > > > perfect sense since there is no way to verify the end result of RW->RX. > > > > > > But for SGX, assuming enclaves are whitelisted by their sigstruct (checked > > > at EINIT) and because page permissions affect sigstruct.MRENCLAVE, it *is* > > > possible to verify the resulting RX contents. E.g. for the purposes of > > > LSMs, can't we use the .sigstruct file as a proxy for the enclave and > > > require FILE__EXECUTE on the .sigstruct inode to map/run the enclave? > > > > > > Stephen, is my logic sound? > > > > > > > > > If so... > > > > > > - Require FILE__READ+FILE__EXECUTE on .sigstruct to mmap() the enclave. > > > > > > - Prevent userspace from mapping the enclave with permissions beyond the > > > original permissions of the enclave. This can be done by populating > > > VM_MAY{READ,WRITE,EXEC} from the SECINFO (same basic concept as Andy's > > > proposals). E.g. pre-EINIT, mmap() and mprotect() can only succeed > > > with PROT_NONE. > > > > > > - Require FILE__{READ,WRITE,EXECUTE} on /dev/sgx/enclave for simplicity, > > > or provide an alternate SGX_IOC_MPROTECT if we want to sidestep the > > > FILE__WRITE requirement. > > > > One more thought. EADD (and the equivalent SGX2 flow) could do > > security_mmap_file() with a NULL file on the SECINFO permissions, which > > would trigger PROCESS_EXECMEM if an enclave attempts to map a page RWX. > > If "initial permissions" for enclaves are less restrictive than shared > objects, then it'd become a backdoor for circumventing LSM when enclave > whitelisting is *not* in place. For example, an adversary may load a page, > which would otherwise never be executable, as an executable page in EPC. My point is that enclaves have different properties than shared objects. Normal LSM behavior with regard to executing files is to label files with e.g. FILE__EXECUTE. Because an enclave must be built to the exact specifications of .sigstruct, requring FILE__EXECUTE on the .sigstruct is effectively the same as requiring FILE__EXECUTE on the enclave itself. Addressing your scenario of loading an executable page in EPC, doing so would require one of the following: - Ability to install a .sigstruct with FILE__EXECUTE - PROCESS__EXECMEM - FILE__EXECMOD and SGX2 support > In the case a RWX page is needed, the calling process has to have a RWX page > serving as the source for EADD so PROCESS__EXECMEM will have been checked. > For SGX2, changing an EPC page to RWX is subject to FILE__EXECMEM on > /dev/sgx/enclave, which I see as a security benefit because it only affects > the enclave but not the whole process hosting it. There is no FILE__EXECMEM check, only PROCESS__EXECMEM and FILE__EXECMOD. I assume you're referring to the latter? I don't see a fundamental difference between having RWX in an enclave and RWX in normal memory, either way the process can execute arbitrary code, i.e. PROCESS__EXECMEM is appropriate. Yes, an enclave will #UD on certain instructions, but that's easily sidestepped by having a trampoline in the host (marked RX) and piping arbitrary code into the enclave. Or using EEXIT to do a bit of ROP. > > > No changes are required to LSMs, SGX1 has a single LSM touchpoint in > > its > > > mmap(), and I *think* the only required userspace change is to mmap() > > > PROT_NONE when allocating the enclave's virtual address range. > > I'm not sure I understand the motivation behind this proposal to decouple > initial EPC permissions from source pages. Pulling permissions from source pages means userspace needs to fully map the in normal memory, including marking pages executable. That exposes the loader to having executable pages in its address space that it has no intention of executing (outside of the enclave). And for Graphene, it means having to actively avoid PROCESS__EXECMEM, e.g. by using a dummy backing file to build the enclave instead of anon memory. > I don't think it a big deal to fully mmap() enclave files, which have to be > parsed by user mode anyway to determine various things including but not > limited to the size of heap(s), size and number of TCSs/stacks/TLS areas, and > the overall enclave size. So with PHDRs parsed, it's trivial to mmap() each > segment with permissions from its PHDR. > > > > As for Graphene, it doesn't need extra permissions to run its enclaves, > > > it just needs a way to install .sigstruct, which is a generic permissions > > > problem and not SGX specific. > > > > > > > > > For SGX2 maybe: > > > > > > - No additional requirements to map an EAUG'd page as RW page. Not > > > aligned with standard MAP_SHARED behavior, but we really don't want > > > to require FILE__WRITE, and thus allow writes to .sigstruct. > > > > > > - Require FILE__EXECMOD on the .sigstruct to map previously writable > > > page as executable (which indirectly includes all EAUG'd pages). > > > Wiring this up will be a little funky, but we again we don't want > > > to require FILE__WRITE on .sigstruct. > > > > > I'm lost. Why is EAUG tied to permissions on .sigstruct? Because for the purposes of LSM checks, .sigstruct is the enclave's backing file, and mapping a previously writable enclave page as exectuable is roughly equivalent to mapping a CoW'd page as exectuable.
On Fri, May 24, 2019 at 12:13 PM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Fri, May 24, 2019 at 11:34:32AM -0700, Xing, Cedric wrote: > > > From: linux-sgx-owner@vger.kernel.org [mailto:linux-sgx- > > > owner@vger.kernel.org] On Behalf Of Sean Christopherson > > > Sent: Friday, May 24, 2019 10:55 AM > I don't see a fundamental difference between having RWX in an enclave and > RWX in normal memory, either way the process can execute arbitrary code, > i.e. PROCESS__EXECMEM is appropriate. Yes, an enclave will #UD on certain > instructions, but that's easily sidestepped by having a trampoline in the > host (marked RX) and piping arbitrary code into the enclave. Or using > EEXIT to do a bit of ROP. There's a difference, albeit a somewhat weak one, if sigstructs are whitelisted. FILE__EXECMOD on either /dev/sgx/enclave or on the sigstruct is not an entirely crazy way to express this.
On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> wrote: > > > From: linux-sgx-owner@vger.kernel.org [mailto:linux-sgx- > > owner@vger.kernel.org] On Behalf Of Sean Christopherson > > Sent: Friday, May 24, 2019 10:55 AM > > > > On Fri, May 24, 2019 at 10:42:43AM -0700, Sean Christopherson wrote: > > > Hmm, I've been thinking more about pulling permissions from the source > > > page. Conceptually I'm not sure we need to meet the same requirements > > as > > > non-enclave DSOs while the enclave is being built, i.e. do we really > > need > > > to force userspace to fully map the enclave in normal memory? > > > > > > Consider the Graphene scenario where it's building an enclave on the > > fly. > > > Pulling permissions from the source VMAs means Graphene has to map the > > > code pages of the enclave with X. This means Graphene will need > > EXEDMOD > > > (or EXECMEM if Graphene isn't careful). In a non-SGX scenario this > > makes > > > perfect sense since there is no way to verify the end result of RW->RX. > > > > > > But for SGX, assuming enclaves are whitelisted by their sigstruct > > (checked > > > at EINIT) and because page permissions affect sigstruct.MRENCLAVE, it > > *is* > > > possible to verify the resulting RX contents. E.g. for the purposes > > of > > > LSMs, can't we use the .sigstruct file as a proxy for the enclave and > > > require FILE__EXECUTE on the .sigstruct inode to map/run the enclave? > > > > > > Stephen, is my logic sound? > > > > > > > > > If so... > > > > > > - Require FILE__READ+FILE__EXECUTE on .sigstruct to mmap() the > > enclave. > > > > > > - Prevent userspace from mapping the enclave with permissions beyond > > the > > > original permissions of the enclave. This can be done by > > populating > > > VM_MAY{READ,WRITE,EXEC} from the SECINFO (same basic concept as > > Andy's > > > proposals). E.g. pre-EINIT, mmap() and mprotect() can only > > succeed > > > with PROT_NONE. > > > > > > - Require FILE__{READ,WRITE,EXECUTE} on /dev/sgx/enclave for > > simplicity, > > > or provide an alternate SGX_IOC_MPROTECT if we want to sidestep > > the > > > FILE__WRITE requirement. > > > > One more thought. EADD (and the equivalent SGX2 flow) could do > > security_mmap_file() with a NULL file on the SECINFO permissions, which > > would trigger PROCESS_EXECMEM if an enclave attempts to map a page RWX. > > If "initial permissions" for enclaves are less restrictive than shared objects, then it'd become a backdoor for circumventing LSM when enclave whitelisting is *not* in place. For example, an adversary may load a page, which would otherwise never be executable, as an executable page in EPC. > > In the case a RWX page is needed, the calling process has to have a RWX page serving as the source for EADD so PROCESS__EXECMEM will have been checked. For SGX2, changing an EPC page to RWX is subject to FILE__EXECMEM on /dev/sgx/enclave, which I see as a security benefit because it only affects the enclave but not the whole process hosting it. So the permission would be like FILE__EXECMOD on the source enclave page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE? MAP_SHARED, PROT_WRITE isn't going to work because that means you can modify the file. I'm starting to think that looking at the source VMA permission bits or source PTE permission bits is putting a bit too much policy into the driver as opposed to the LSM. How about delegating the whole thing to an LSM hook? The EADD operation would invoke a new hook, something like: int security_enclave_load_bytes(void *source_addr, struct vm_area_struct *source_vma, loff_t source_offset, unsigned int maxperm); Then you don't have to muck with mapping anything PROT_EXEC. Instead you load from a mapping of a file and the LSM applies whatever policy it feels appropriate. If the first pass gets something wrong, the application or library authors can take it up with the SELinux folks without breaking the whole ABI :) (I'm proposing passing in the source_vma because this hook would be called with mmap_sem held for read to avoid a TOCTOU race.) If we go this route, the only substantial change to the existing driver that's needed for an initial upstream merge is the maxperm mechanism and whatever hopefully minimal API changes are needed to allow users to conveniently set up the mappings. And we don't need to worry about how to hack around mprotect() calling into the LSM, because the LSM will actually be aware of SGX and can just do the right thing. --Andy
On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote: > On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> wrote: > > > > If "initial permissions" for enclaves are less restrictive than shared > > objects, then it'd become a backdoor for circumventing LSM when enclave > > whitelisting is *not* in place. For example, an adversary may load a page, > > which would otherwise never be executable, as an executable page in EPC. > > > > In the case a RWX page is needed, the calling process has to have a RWX > > page serving as the source for EADD so PROCESS__EXECMEM will have been > > checked. For SGX2, changing an EPC page to RWX is subject to FILE__EXECMEM > > on /dev/sgx/enclave, which I see as a security benefit because it only > > affects the enclave but not the whole process hosting it. > > So the permission would be like FILE__EXECMOD on the source enclave > page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE? > MAP_SHARED, PROT_WRITE isn't going to work because that means you can > modify the file. Was this in response to Cedric's comment, or to my comment? > I'm starting to think that looking at the source VMA permission bits > or source PTE permission bits is putting a bit too much policy into > the driver as opposed to the LSM. How about delegating the whole > thing to an LSM hook? The EADD operation would invoke a new hook, > something like: > > int security_enclave_load_bytes(void *source_addr, struct > vm_area_struct *source_vma, loff_t source_offset, unsigned int > maxperm); > > Then you don't have to muck with mapping anything PROT_EXEC. Instead > you load from a mapping of a file and the LSM applies whatever policy > it feels appropriate. If the first pass gets something wrong, the > application or library authors can take it up with the SELinux folks > without breaking the whole ABI :) > > (I'm proposing passing in the source_vma because this hook would be > called with mmap_sem held for read to avoid a TOCTOU race.) > > If we go this route, the only substantial change to the existing > driver that's needed for an initial upstream merge is the maxperm > mechanism and whatever hopefully minimal API changes are needed to > allow users to conveniently set up the mappings. And we don't need to > worry about how to hack around mprotect() calling into the LSM, > because the LSM will actually be aware of SGX and can just do the > right thing. This doesn't address restricting which processes can run which enclaves, it only allows restricting the build flow. Or are you suggesting this be done in addition to whitelisting sigstructs? What's the value prop beyond whitelisting sigstructs? Realistically, I doubt LSMs/users will want to take the performance hit of scanning the source bytes every time an enclave is loaded. We could add seomthing like security_enclave_mprotect() in lieu of abusing security_file_mprotect(), but passing the full source bytes seems a bit much.
> From: linux-sgx-owner@vger.kernel.org [mailto:linux-sgx- > owner@vger.kernel.org] On Behalf Of Sean Christopherson > Sent: Friday, May 24, 2019 12:14 PM > > My point is that enclaves have different properties than shared objects. > > Normal LSM behavior with regard to executing files is to label files > with e.g. FILE__EXECUTE. Because an enclave must be built to the exact > specifications of .sigstruct, requring FILE__EXECUTE on the .sigstruct > is effectively the same as requiring FILE__EXECUTE on the enclave itself. > > Addressing your scenario of loading an executable page in EPC, doing so > would require one of the following: > > - Ability to install a .sigstruct with FILE__EXECUTE > > - PROCESS__EXECMEM > > - FILE__EXECMOD and SGX2 support Now I got your point. It sounds a great idea to me! But instead of using .sigstruct file, I'd still recommend using file mapping (i.e. SIGSTRUCT needs to reside in executable memory). But then there'll be a hole - a process having FILE__EXECMOD on any file could use that file as a SIGSTRUCT. Probably we'll need a new type in SELinux to label enclave/sigstruct files. > > > In the case a RWX page is needed, the calling process has to have a > > RWX page serving as the source for EADD so PROCESS__EXECMEM will have > been checked. > > For SGX2, changing an EPC page to RWX is subject to FILE__EXECMEM on > > /dev/sgx/enclave, which I see as a security benefit because it only > > affects the enclave but not the whole process hosting it. > > There is no FILE__EXECMEM check, only PROCESS__EXECMEM and FILE__EXECMOD. > I assume you're referring to the latter? Yes. > > I don't see a fundamental difference between having RWX in an enclave > and RWX in normal memory, either way the process can execute arbitrary > code, i.e. PROCESS__EXECMEM is appropriate. Yes, an enclave will #UD on > certain instructions, but that's easily sidestepped by having a > trampoline in the host (marked RX) and piping arbitrary code into the > enclave. Or using EEXIT to do a bit of ROP. I'm with you. With your proposal only FILE__EXECMOD is needed on /dev/sgx/enclave to launch Graphene enclaves or the like. > > > > > No changes are required to LSMs, SGX1 has a single LSM touchpoint > > > > in > > > its > > > > mmap(), and I *think* the only required userspace change is to > > > > mmap() PROT_NONE when allocating the enclave's virtual address > range. > > > > I'm not sure I understand the motivation behind this proposal to > > decouple initial EPC permissions from source pages. > > Pulling permissions from source pages means userspace needs to fully map > the in normal memory, including marking pages executable. That exposes > the loader to having executable pages in its address space that it has > no intention of executing (outside of the enclave). And for Graphene, > it means having to actively avoid PROCESS__EXECMEM, e.g. by using a > dummy backing file to build the enclave instead of anon memory. Agreed. > > > I don't think it a big deal to fully mmap() enclave files, which have > > to be parsed by user mode anyway to determine various things including > > but not limited to the size of heap(s), size and number of > > TCSs/stacks/TLS areas, and the overall enclave size. So with PHDRs > > parsed, it's trivial to mmap() each segment with permissions from its > PHDR. > > > > > > As for Graphene, it doesn't need extra permissions to run its > > > > enclaves, it just needs a way to install .sigstruct, which is a > > > > generic permissions problem and not SGX specific. > > > > > > > > > > > > For SGX2 maybe: > > > > > > > > - No additional requirements to map an EAUG'd page as RW page. > Not > > > > aligned with standard MAP_SHARED behavior, but we really don't > want > > > > to require FILE__WRITE, and thus allow writes to .sigstruct. > > > > > > > > - Require FILE__EXECMOD on the .sigstruct to map previously > writable > > > > page as executable (which indirectly includes all EAUG'd > pages). > > > > Wiring this up will be a little funky, but we again we don't > want > > > > to require FILE__WRITE on .sigstruct. > > > > > > > > I'm lost. Why is EAUG tied to permissions on .sigstruct? > > Because for the purposes of LSM checks, .sigstruct is the enclave's > backing file, and mapping a previously writable enclave page as > exectuable is roughly equivalent to mapping a CoW'd page as exectuable. I think I've got your idea. You are trying to use permissions on .sigstruct to determine whether EAUG will be available to that specific enclave. Am I right? I'd tie EAUG to the permissions of /dev/sgx/enclave instead. But why? There are couple of reasons. For one, a SIGSTRUCT identifies the behavior of the enclave, hence the SGX features needed by that enclave. So if an enclave requires EAUG, the .sigstruct has to allow EAUG or the enclave wouldn't work. That means the system admin wouldn't have a choice but to match up what's needed by the enclave. For two, whether to allow, say loading code dynamically into an enclave, depends on whether the host process can tolerate the inherent risk. And that decision is seldom made on individual enclaves but to the host process as a whole. And /dev/sgx/enclave serves that purpose. -Cedric
> From: linux-sgx-owner@vger.kernel.org [mailto:linux-sgx- > owner@vger.kernel.org] On Behalf Of Sean Christopherson > Sent: Friday, May 24, 2019 1:04 PM > > On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote: > > On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> > wrote: > > > > > > If "initial permissions" for enclaves are less restrictive than > > > shared objects, then it'd become a backdoor for circumventing LSM > > > when enclave whitelisting is *not* in place. For example, an > > > adversary may load a page, which would otherwise never be executable, > as an executable page in EPC. > > > > > > In the case a RWX page is needed, the calling process has to have a > > > RWX page serving as the source for EADD so PROCESS__EXECMEM will > > > have been checked. For SGX2, changing an EPC page to RWX is subject > > > to FILE__EXECMEM on /dev/sgx/enclave, which I see as a security > > > benefit because it only affects the enclave but not the whole > process hosting it. > > > > So the permission would be like FILE__EXECMOD on the source enclave > > page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE? > > MAP_SHARED, PROT_WRITE isn't going to work because that means you can > > modify the file. > > Was this in response to Cedric's comment, or to my comment? Creating RWX source page requires PROCESS_EXECMEM. But as I responded to Sean earlier, I think his proposal of "aggregating" all "initial" permission checks into a single SIGSTRUCT check is probably a better approach. > > > I'm starting to think that looking at the source VMA permission bits > > or source PTE permission bits is putting a bit too much policy into > > the driver as opposed to the LSM. How about delegating the whole > > thing to an LSM hook? The EADD operation would invoke a new hook, > > something like: > > > > int security_enclave_load_bytes(void *source_addr, struct > > vm_area_struct *source_vma, loff_t source_offset, unsigned int > > maxperm); This is exactly what I was thinking. But with Sean's proposal this is probably no longer necessary. > > > > Then you don't have to muck with mapping anything PROT_EXEC. Instead > > you load from a mapping of a file and the LSM applies whatever policy > > it feels appropriate. If the first pass gets something wrong, the > > application or library authors can take it up with the SELinux folks > > without breaking the whole ABI :) > > > > (I'm proposing passing in the source_vma because this hook would be > > called with mmap_sem held for read to avoid a TOCTOU race.) > > > > If we go this route, the only substantial change to the existing > > driver that's needed for an initial upstream merge is the maxperm > > mechanism and whatever hopefully minimal API changes are needed to > > allow users to conveniently set up the mappings. And we don't need to > > worry about how to hack around mprotect() calling into the LSM, > > because the LSM will actually be aware of SGX and can just do the > > right thing. > > This doesn't address restricting which processes can run which enclaves, > it only allows restricting the build flow. Or are you suggesting this > be done in addition to whitelisting sigstructs? In the context of SELinux, new types could be defined to be associated with SIGSTRUCT (or more precisely, files containing SIGSTRUCTs). Then the LSM hook (I'd propose security_sgx_initialize_enclave) could enforce whatever... > > What's the value prop beyond whitelisting sigstructs? Realistically, I > doubt LSMs/users will want to take the performance hit of scanning the > source bytes every time an enclave is loaded. > > We could add seomthing like security_enclave_mprotect() in lieu of > abusing security_file_mprotect(), but passing the full source bytes > seems a bit much. I'd just use /dev/sgx/enclave to govern "runtime" permissions any EPC page can mmap()/mprotect() to. Then we won't need any code changes in LSM. -Cedric
On Fri, May 24, 2019 at 01:42:13PM -0700, Xing, Cedric wrote: > > From: linux-sgx-owner@vger.kernel.org [mailto:linux-sgx- > > owner@vger.kernel.org] On Behalf Of Sean Christopherson > > Sent: Friday, May 24, 2019 12:14 PM > > > > My point is that enclaves have different properties than shared objects. > > > > Normal LSM behavior with regard to executing files is to label files > > with e.g. FILE__EXECUTE. Because an enclave must be built to the exact > > specifications of .sigstruct, requring FILE__EXECUTE on the .sigstruct > > is effectively the same as requiring FILE__EXECUTE on the enclave itself. > > > > Addressing your scenario of loading an executable page in EPC, doing so > > would require one of the following: > > > > - Ability to install a .sigstruct with FILE__EXECUTE > > > > - PROCESS__EXECMEM > > > > - FILE__EXECMOD and SGX2 support > > Now I got your point. It sounds a great idea to me! > > But instead of using .sigstruct file, I'd still recommend using file mapping > (i.e. SIGSTRUCT needs to reside in executable memory). But then there'll be a Why? Even in the Graphene case the final .sigstruct can be known ahead of time. Userspace can always use memfd() if it's generating SIGSTRUCT on the fly. > hole - a process having FILE__EXECMOD on any file could use that file as a > SIGSTRUCT. Probably we'll need a new type in SELinux to label > enclave/sigstruct files. > > > I don't see a fundamental difference between having RWX in an enclave > > and RWX in normal memory, either way the process can execute arbitrary > > code, i.e. PROCESS__EXECMEM is appropriate. Yes, an enclave will #UD on > > certain instructions, but that's easily sidestepped by having a > > trampoline in the host (marked RX) and piping arbitrary code into the > > enclave. Or using EEXIT to do a bit of ROP. > > I'm with you. > > With your proposal only FILE__EXECMOD is needed on /dev/sgx/enclave to launch > Graphene enclaves or the like. It wouldn't even need FILE__EXECMOD, assuming Graphene does all of its libc rewriting before building the enclave, i.e. doesn't EADD RWX pages. > > > > > No changes are required to LSMs, SGX1 has a single LSM touchpoint > > > > > in > > > > its > > > > > mmap(), and I *think* the only required userspace change is to > > > > > mmap() PROT_NONE when allocating the enclave's virtual address > > range. > > > > > > I'm not sure I understand the motivation behind this proposal to > > > decouple initial EPC permissions from source pages. > > > > Pulling permissions from source pages means userspace needs to fully map > > the in normal memory, including marking pages executable. That exposes > > the loader to having executable pages in its address space that it has > > no intention of executing (outside of the enclave). And for Graphene, > > it means having to actively avoid PROCESS__EXECMEM, e.g. by using a > > dummy backing file to build the enclave instead of anon memory. > > Agreed. > > > > > > I don't think it a big deal to fully mmap() enclave files, which have > > > to be parsed by user mode anyway to determine various things including > > > but not limited to the size of heap(s), size and number of > > > TCSs/stacks/TLS areas, and the overall enclave size. So with PHDRs > > > parsed, it's trivial to mmap() each segment with permissions from its > > PHDR. > > > > > > > > As for Graphene, it doesn't need extra permissions to run its > > > > > enclaves, it just needs a way to install .sigstruct, which is a > > > > > generic permissions problem and not SGX specific. > > > > > > > > > > > > > > > For SGX2 maybe: > > > > > > > > > > - No additional requirements to map an EAUG'd page as RW page. > > Not > > > > > aligned with standard MAP_SHARED behavior, but we really don't > > want > > > > > to require FILE__WRITE, and thus allow writes to .sigstruct. > > > > > > > > > > - Require FILE__EXECMOD on the .sigstruct to map previously > > writable > > > > > page as executable (which indirectly includes all EAUG'd > > pages). > > > > > Wiring this up will be a little funky, but we again we don't > > want > > > > > to require FILE__WRITE on .sigstruct. > > > > > > > > > > > I'm lost. Why is EAUG tied to permissions on .sigstruct? > > > > Because for the purposes of LSM checks, .sigstruct is the enclave's > > backing file, and mapping a previously writable enclave page as > > exectuable is roughly equivalent to mapping a CoW'd page as exectuable. > > I think I've got your idea. You are trying to use permissions on .sigstruct > to determine whether EAUG will be available to that specific enclave. Am I > right? Yep. > I'd tie EAUG to the permissions of /dev/sgx/enclave instead. But why? There > are couple of reasons. For one, a SIGSTRUCT identifies the behavior of the > enclave, hence the SGX features needed by that enclave. So if an enclave > requires EAUG, the .sigstruct has to allow EAUG or the enclave wouldn't work. > That means the system admin wouldn't have a choice but to match up what's > needed by the enclave. For two, whether to allow, say loading code > dynamically into an enclave, depends on whether the host process can tolerate > the inherent risk. And that decision is seldom made on individual enclaves > but to the host process as a whole. And /dev/sgx/enclave serves that purpose. I think I'd be ok either way? What I really care about is having line of sight to a sane way to support for SGX2, and both seem sane. I.e. we can hash this detail out when SGX2 goes in.
On Fri, May 24, 2019 at 1:03 PM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote: > > On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> wrote: > > > > > > If "initial permissions" for enclaves are less restrictive than shared > > > objects, then it'd become a backdoor for circumventing LSM when enclave > > > whitelisting is *not* in place. For example, an adversary may load a page, > > > which would otherwise never be executable, as an executable page in EPC. > > > > > > In the case a RWX page is needed, the calling process has to have a RWX > > > page serving as the source for EADD so PROCESS__EXECMEM will have been > > > checked. For SGX2, changing an EPC page to RWX is subject to FILE__EXECMEM > > > on /dev/sgx/enclave, which I see as a security benefit because it only > > > affects the enclave but not the whole process hosting it. > > > > So the permission would be like FILE__EXECMOD on the source enclave > > page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE? > > MAP_SHARED, PROT_WRITE isn't going to work because that means you can > > modify the file. > > Was this in response to Cedric's comment, or to my comment? Yours. I think that requiring source pages to be actually mapped W is not such a great idea. > > > I'm starting to think that looking at the source VMA permission bits > > or source PTE permission bits is putting a bit too much policy into > > the driver as opposed to the LSM. How about delegating the whole > > thing to an LSM hook? The EADD operation would invoke a new hook, > > something like: > > > > int security_enclave_load_bytes(void *source_addr, struct > > vm_area_struct *source_vma, loff_t source_offset, unsigned int > > maxperm); > > > > Then you don't have to muck with mapping anything PROT_EXEC. Instead > > you load from a mapping of a file and the LSM applies whatever policy > > it feels appropriate. If the first pass gets something wrong, the > > application or library authors can take it up with the SELinux folks > > without breaking the whole ABI :) > > > > (I'm proposing passing in the source_vma because this hook would be > > called with mmap_sem held for read to avoid a TOCTOU race.) > > > > If we go this route, the only substantial change to the existing > > driver that's needed for an initial upstream merge is the maxperm > > mechanism and whatever hopefully minimal API changes are needed to > > allow users to conveniently set up the mappings. And we don't need to > > worry about how to hack around mprotect() calling into the LSM, > > because the LSM will actually be aware of SGX and can just do the > > right thing. > > This doesn't address restricting which processes can run which enclaves, > it only allows restricting the build flow. Or are you suggesting this > be done in addition to whitelisting sigstructs? In addition. But I named the function badly and gave it a bad signature, which confused you. Let's try again: int security_enclave_load_from_memory(const struct vm_area_struct *source, unsigned int maxperm); Maybe some really fancy future LSM would also want loff_t source_offset, but it's probably not terribly useful. This same callback would be used for EAUG. Following up on your discussion with Cedric about sigstruct, the other callback would be something like: int security_enclave_init(struct file *sigstruct_file); The main issue I see is that we also want to control the enclave's ability to have RWX pages or to change a W page to X. We might also want: int security_enclave_load_zeros(unsigned int maxperm); An enclave that's going to modify its own code will need memory with maxperm = RWX or WX. But this is a bit awkward if the LSM's decision depends on the sigstruct. We could get fancy and require that the sigstruct be supplied before any EADD operations so that the maxperm decisions can depend on the sigstruct. Am I making more sense now?
On Fri, May 24, 2019 at 02:27:34PM -0700, Andy Lutomirski wrote: > On Fri, May 24, 2019 at 1:03 PM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote: > > > On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> wrote: > > > > > > > > If "initial permissions" for enclaves are less restrictive than shared > > > > objects, then it'd become a backdoor for circumventing LSM when enclave > > > > whitelisting is *not* in place. For example, an adversary may load a page, > > > > which would otherwise never be executable, as an executable page in EPC. > > > > > > > > In the case a RWX page is needed, the calling process has to have a RWX > > > > page serving as the source for EADD so PROCESS__EXECMEM will have been > > > > checked. For SGX2, changing an EPC page to RWX is subject to FILE__EXECMEM > > > > on /dev/sgx/enclave, which I see as a security benefit because it only > > > > affects the enclave but not the whole process hosting it. > > > > > > So the permission would be like FILE__EXECMOD on the source enclave > > > page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE? > > > MAP_SHARED, PROT_WRITE isn't going to work because that means you can > > > modify the file. > > > > Was this in response to Cedric's comment, or to my comment? > > Yours. I think that requiring source pages to be actually mapped W is > not such a great idea. I wasn't requiring source pages to be mapped W. At least I didn't intend to require W. What I was trying to say is that SGX could trigger an EXECMEM check if userspace attempted to EADD or EAUG an enclave page with RWX permissions, e.g.: if ((SECINFO.PERMS & RWX) == RWX) { ret = security_mmap_file(NULL, RWX, ???); if (ret) return ret; } But that's a moot point if we add security_enclave_load() or whatever. > > > > > > I'm starting to think that looking at the source VMA permission bits > > > or source PTE permission bits is putting a bit too much policy into > > > the driver as opposed to the LSM. How about delegating the whole > > > thing to an LSM hook? The EADD operation would invoke a new hook, > > > something like: > > > > > > int security_enclave_load_bytes(void *source_addr, struct > > > vm_area_struct *source_vma, loff_t source_offset, unsigned int > > > maxperm); > > > > > > Then you don't have to muck with mapping anything PROT_EXEC. Instead > > > you load from a mapping of a file and the LSM applies whatever policy > > > it feels appropriate. If the first pass gets something wrong, the > > > application or library authors can take it up with the SELinux folks > > > without breaking the whole ABI :) > > > > > > (I'm proposing passing in the source_vma because this hook would be > > > called with mmap_sem held for read to avoid a TOCTOU race.) > > > > > > If we go this route, the only substantial change to the existing > > > driver that's needed for an initial upstream merge is the maxperm > > > mechanism and whatever hopefully minimal API changes are needed to > > > allow users to conveniently set up the mappings. And we don't need to > > > worry about how to hack around mprotect() calling into the LSM, > > > because the LSM will actually be aware of SGX and can just do the > > > right thing. > > > > This doesn't address restricting which processes can run which enclaves, > > it only allows restricting the build flow. Or are you suggesting this > > be done in addition to whitelisting sigstructs? > > In addition. > > But I named the function badly and gave it a bad signature, which > confused you. Let's try again: > > int security_enclave_load_from_memory(const struct vm_area_struct > *source, unsigned int maxperm); I prefer security_enclave_load(), "from_memory" seems redundant at best. > Maybe some really fancy future LSM would also want loff_t > source_offset, but it's probably not terribly useful. This same > callback would be used for EAUG. > > Following up on your discussion with Cedric about sigstruct, the other > callback would be something like: > > int security_enclave_init(struct file *sigstruct_file); > > The main issue I see is that we also want to control the enclave's > ability to have RWX pages or to change a W page to X. We might also > want: > > int security_enclave_load_zeros(unsigned int maxperm); What's the use case for this? @maxperm will always be at least RW in this case, otherwise the page is useless to the enclave, and if the enclave can write the page, the fact that it started as zeros is irrelevant. > An enclave that's going to modify its own code will need memory with > maxperm = RWX or WX. > > But this is a bit awkward if the LSM's decision depends on the > sigstruct. We could get fancy and require that the sigstruct be > supplied before any EADD operations so that the maxperm decisions can > depend on the sigstruct. > > Am I making more sense now? Yep. Requiring .sigstruct at ECREATE would be trivial. If we wanted flexibility we could do: int security_enclave_load(struct file *file, struct vm_area_struct *vma, unsigned long prot); And for ultimate flexibility we could pass both .sigstruct and the file pointer for /dev/sgx/enclave, but that seems a bit ridiculous. Passing both would allow tying EXECMOD to /dev/sgx/enclave as Cedric wanted (without having to play games and pass /dev/sgx/enclave to security_enclave_load()), but I don't think there's anything fundamentally broken with using .sigstruct for EXECMOD. It requires more verbose labeling, but that's not a bad thing.
> On May 24, 2019, at 3:41 PM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > >> On Fri, May 24, 2019 at 02:27:34PM -0700, Andy Lutomirski wrote: >> On Fri, May 24, 2019 at 1:03 PM Sean Christopherson >> <sean.j.christopherson@intel.com> wrote: >>> >>>> On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote: >>>>> On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> wrote: >>>>> >>>>> If "initial permissions" for enclaves are less restrictive than shared >>>>> objects, then it'd become a backdoor for circumventing LSM when enclave >>>>> whitelisting is *not* in place. For example, an adversary may load a page, >>>>> which would otherwise never be executable, as an executable page in EPC. >>>>> >>>>> In the case a RWX page is needed, the calling process has to have a RWX >>>>> page serving as the source for EADD so PROCESS__EXECMEM will have been >>>>> checked. For SGX2, changing an EPC page to RWX is subject to FILE__EXECMEM >>>>> on /dev/sgx/enclave, which I see as a security benefit because it only >>>>> affects the enclave but not the whole process hosting it. >>>> >>>> So the permission would be like FILE__EXECMOD on the source enclave >>>> page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE? >>>> MAP_SHARED, PROT_WRITE isn't going to work because that means you can >>>> modify the file. >>> >>> Was this in response to Cedric's comment, or to my comment? >> >> Yours. I think that requiring source pages to be actually mapped W is >> not such a great idea. > > I wasn't requiring source pages to be mapped W. At least I didn't intend > to require W. What I was trying to say is that SGX could trigger an > EXECMEM check if userspace attempted to EADD or EAUG an enclave page with > RWX permissions, e.g.: > > if ((SECINFO.PERMS & RWX) == RWX) { > ret = security_mmap_file(NULL, RWX, ???); > if (ret) > return ret; > } > > But that's a moot point if we add security_enclave_load() or whatever. > >> >>> >>>> I'm starting to think that looking at the source VMA permission bits >>>> or source PTE permission bits is putting a bit too much policy into >>>> the driver as opposed to the LSM. How about delegating the whole >>>> thing to an LSM hook? The EADD operation would invoke a new hook, >>>> something like: >>>> >>>> int security_enclave_load_bytes(void *source_addr, struct >>>> vm_area_struct *source_vma, loff_t source_offset, unsigned int >>>> maxperm); >>>> >>>> Then you don't have to muck with mapping anything PROT_EXEC. Instead >>>> you load from a mapping of a file and the LSM applies whatever policy >>>> it feels appropriate. If the first pass gets something wrong, the >>>> application or library authors can take it up with the SELinux folks >>>> without breaking the whole ABI :) >>>> >>>> (I'm proposing passing in the source_vma because this hook would be >>>> called with mmap_sem held for read to avoid a TOCTOU race.) >>>> >>>> If we go this route, the only substantial change to the existing >>>> driver that's needed for an initial upstream merge is the maxperm >>>> mechanism and whatever hopefully minimal API changes are needed to >>>> allow users to conveniently set up the mappings. And we don't need to >>>> worry about how to hack around mprotect() calling into the LSM, >>>> because the LSM will actually be aware of SGX and can just do the >>>> right thing. >>> >>> This doesn't address restricting which processes can run which enclaves, >>> it only allows restricting the build flow. Or are you suggesting this >>> be done in addition to whitelisting sigstructs? >> >> In addition. >> >> But I named the function badly and gave it a bad signature, which >> confused you. Let's try again: >> >> int security_enclave_load_from_memory(const struct vm_area_struct >> *source, unsigned int maxperm); > > I prefer security_enclave_load(), "from_memory" seems redundant at best. Fine with me. > >> Maybe some really fancy future LSM would also want loff_t >> source_offset, but it's probably not terribly useful. This same >> callback would be used for EAUG. >> >> Following up on your discussion with Cedric about sigstruct, the other >> callback would be something like: >> >> int security_enclave_init(struct file *sigstruct_file); >> >> The main issue I see is that we also want to control the enclave's >> ability to have RWX pages or to change a W page to X. We might also >> want: >> >> int security_enclave_load_zeros(unsigned int maxperm); > > What's the use case for this? @maxperm will always be at least RW in > this case, otherwise the page is useless to the enclave, and if the > enclave can write the page, the fact that it started as zeros is > irrelevant. This is how EAUG could ask if RWX is okay. If an enclave is internally doing dynamic loading, the it will need a heap page with maxperm = RWX. (If it’s well designed, it will make it RW and then RX, either by changing SECINFO or by asking the host to mprotect() it, but it still needs the overall RWX mask.). Also, do real SGX1 enclave formats have BSS? If so, then either we need an ioctl or load zeros or user code is going to load from /dev/zero or just from the heap, but the LSM is going to play better with an ioctl, I suspect :) > >> An enclave that's going to modify its own code will need memory with >> maxperm = RWX or WX. >> >> But this is a bit awkward if the LSM's decision depends on the >> sigstruct. We could get fancy and require that the sigstruct be >> supplied before any EADD operations so that the maxperm decisions can >> depend on the sigstruct. >> >> Am I making more sense now? > > Yep. Requiring .sigstruct at ECREATE would be trivial. If we wanted > flexibility we could do: > > int security_enclave_load(struct file *file, struct vm_area_struct *vma, > unsigned long prot); > > And for ultimate flexibility we could pass both .sigstruct and the file > pointer for /dev/sgx/enclave, but that seems a bit ridiculous. I agree. > > Passing both would allow tying EXECMOD to /dev/sgx/enclave as Cedric > wanted (without having to play games and pass /dev/sgx/enclave to > security_enclave_load()), but I don't think there's anything fundamentally > broken with using .sigstruct for EXECMOD. It requires more verbose > labeling, but that's not a bad thing. The benefit of putting it on .sigstruct is that it can be per-enclave. As I understand it from Fedora packaging, the way this works on distros is generally that a package will include some files and their associated labels, and, if the package needs EXECMOD, then the files are labeled with EXECMOD and the author of the relevant code might get a dirty look. This could translate to the author of an exclave that needs RWX regions getting a dirty look without leaking this permission into other enclaves. (In my opinion, the dirty looks are actually the best security benefit of the entire concept of LSMs making RWX difficult. A sufficiently creative attacker can almost always bypass W^X restrictions once they’ve pwned you, but W^X makes it harder to pwn you in the first place, and SELinux makes it really obvious when packaging a program that doesn’t respect W^X. The upshot is that a lot of programs got fixed.)
On Fri, May 24, 2019 at 01:03:33PM -0700, Sean Christopherson wrote: Good morning, I hope the weekend is going well for everyone. Skunky holiday weather out here in West-Central Minnesota. > On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote: > > If we go this route, the only substantial change to the existing > > driver that's needed for an initial upstream merge is the maxperm > > mechanism and whatever hopefully minimal API changes are needed to > > allow users to conveniently set up the mappings. And we don't need to > > worry about how to hack around mprotect() calling into the LSM, > > because the LSM will actually be aware of SGX and can just do the > > right thing. > This doesn't address restricting which processes can run which > enclaves, it only allows restricting the build flow. Or are you > suggesting this be done in addition to whitelisting sigstructs? > > What's the value prop beyond whitelisting sigstructs? > Realistically, I doubt LSMs/users will want to take the performance > hit of scanning the source bytes every time an enclave is loaded. > > We could add seomthing like security_enclave_mprotect() in lieu of > abusing security_file_mprotect(), but passing the full source bytes > seems a bit much. It would seem that we hold the moniker of responsibility for this conversation, since without our provocation regarding cryptographic verification of enclave source, there would be a driver headed upstream whose only constraint against W^X sourced executable code, running with full confidentiality and integrity protections, would be a character device with o666 permissions. Given that, a couple of reflections to facilitate further conversation, if nothing else for the benefit of Jonathan Corbet and his bystanders... :-) As the conversations to date have indicated, imposing LSM controls on enclave executable code is a bit problematic, in no small part since it is the theological equivalent of driving a square peg into a round hole. SGX, as a technology, was designed around the concept of cryptographic verification of code provenance and origin. The decision to take that off the table, for reasons of political idealogy only, means that mainstream Linux will not be a platform that can achieve the full hardware security capabilities and protections of SGX, nor will mainstream Linux be able to enjoy full protections from the technology itself. We will be dealing with that, from a driver and runtime perspective, but that is a conversation for another day. The issue of SGX2 and Enclave Dynamic Memory Management (EDMM) has come up and to date there doesn't appear to have been a serious conversation regarding whether or not all of the LSM machinations in the world will make any difference when this technology goes mainline. The agenda driving mainlining of the driver is to support Graphene for cloud based solutions and without EDMM, dynamic code loading support is decidedly more problematic. Dynamic enclave code loading isn't problematic from a security perspective when the code is being loaded from the platform itself, since presumably, the encompassing conversation will result in LSM controls being applied to the necessary code paths. However, with the ability to exploit SGX2 instructions, an enclave with adverserial intent could simply setup a mutually attested security context and pull whatever executable code it wants from the INTERNET at large, using an encrypted and integrity protected communications channel. That has at least been our interpretation and experience with the ENCLU[EMODPE] and ENCLU[EACCEPTCOPY] instructions and the out-of-tree driver. Given the use of an encrypted channel, and the fact that these instructions are ring 3 enclave mode only, it would seem that all of the LSM controls in the world won't have visibility or control over code that is being loaded and executed using such a mechanism. We could have arguably missed something that the new driver will do to address this issue. To date the only discussion seems to have been about controls over ENCLS[EAUG], which are arguably a bit blunt for this purpose. In the land of SGX, if one is intellectually honest from an engineering perspective, the only solid security contract one has to work with is the notion of cryptographic identity. Hence our concern and patches that implemented an absolutely minimal footprint ring-0 control infrastructure over the contents of an enclave's SIGSTRUCT. Which is where we have arguably circled back to after 3-4 months and one kernel release cycle. Wrapping an LSM hook around our policy mechanism would seem to achieve, from a security perspective, about the same level of security effect that more major and invasive modifications would achieve, given Cedric's proposal to inherit page permissions from the source, which is what our runtime already does. As always, apologies for excessive verbosity beyond LKML sensibilities. Best wishes for a pleasant remainder of the spring weekend to everyone. Dr. Greg As always, Dr. G.W. Wettstein, Ph.D. Enjellic Systems Development, LLC. 4206 N. 19th Ave. Specializing in information infra-structure Fargo, ND 58102 development. PH: 701-281-1686 FAX: 701-281-3949 EMAIL: greg@enjellic.com ------------------------------------------------------------------------------ "Heaven goes by favor. If it went by merit, you would stay out and your dog would go in." -- Mark Twain
> From: Andy Lutomirski [mailto:luto@amacapital.net] > Sent: Friday, May 24, 2019 4:42 PM > > > On May 24, 2019, at 3:41 PM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > > >> On Fri, May 24, 2019 at 02:27:34PM -0700, Andy Lutomirski wrote: > >> On Fri, May 24, 2019 at 1:03 PM Sean Christopherson > >> <sean.j.christopherson@intel.com> wrote: > >>> > >>>> On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote: > >>>>> On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> wrote: > >>>>> > >>>>> If "initial permissions" for enclaves are less restrictive than > >>>>> shared objects, then it'd become a backdoor for circumventing LSM > >>>>> when enclave whitelisting is *not* in place. For example, an > >>>>> adversary may load a page, which would otherwise never be executable, as an executable > page in EPC. > >>>>> > >>>>> In the case a RWX page is needed, the calling process has to have > >>>>> a RWX page serving as the source for EADD so PROCESS__EXECMEM will > >>>>> have been checked. For SGX2, changing an EPC page to RWX is > >>>>> subject to FILE__EXECMEM on /dev/sgx/enclave, which I see as a > >>>>> security benefit because it only affects the enclave but not the whole process hosting > it. > >>>> > >>>> So the permission would be like FILE__EXECMOD on the source enclave > >>>> page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE? > >>>> MAP_SHARED, PROT_WRITE isn't going to work because that means you > >>>> can modify the file. > >>> > >>> Was this in response to Cedric's comment, or to my comment? > >> > >> Yours. I think that requiring source pages to be actually mapped W > >> is not such a great idea. > > > > I wasn't requiring source pages to be mapped W. At least I didn't > > intend to require W. What I was trying to say is that SGX could > > trigger an EXECMEM check if userspace attempted to EADD or EAUG an > > enclave page with RWX permissions, e.g.: > > > > if ((SECINFO.PERMS & RWX) == RWX) { > > ret = security_mmap_file(NULL, RWX, ???); > > if (ret) > > return ret; > > } > > > > But that's a moot point if we add security_enclave_load() or whatever. > > > >> > >>> > >>>> I'm starting to think that looking at the source VMA permission > >>>> bits or source PTE permission bits is putting a bit too much policy > >>>> into the driver as opposed to the LSM. How about delegating the > >>>> whole thing to an LSM hook? The EADD operation would invoke a new > >>>> hook, something like: > >>>> > >>>> int security_enclave_load_bytes(void *source_addr, struct > >>>> vm_area_struct *source_vma, loff_t source_offset, unsigned int > >>>> maxperm); > >>>> > >>>> Then you don't have to muck with mapping anything PROT_EXEC. > >>>> Instead you load from a mapping of a file and the LSM applies > >>>> whatever policy it feels appropriate. If the first pass gets > >>>> something wrong, the application or library authors can take it up > >>>> with the SELinux folks without breaking the whole ABI :) > >>>> > >>>> (I'm proposing passing in the source_vma because this hook would be > >>>> called with mmap_sem held for read to avoid a TOCTOU race.) > >>>> > >>>> If we go this route, the only substantial change to the existing > >>>> driver that's needed for an initial upstream merge is the maxperm > >>>> mechanism and whatever hopefully minimal API changes are needed to > >>>> allow users to conveniently set up the mappings. And we don't need > >>>> to worry about how to hack around mprotect() calling into the LSM, > >>>> because the LSM will actually be aware of SGX and can just do the > >>>> right thing. > >>> > >>> This doesn't address restricting which processes can run which > >>> enclaves, it only allows restricting the build flow. Or are you > >>> suggesting this be done in addition to whitelisting sigstructs? > >> > >> In addition. > >> > >> But I named the function badly and gave it a bad signature, which > >> confused you. Let's try again: > >> > >> int security_enclave_load_from_memory(const struct vm_area_struct > >> *source, unsigned int maxperm); > > > > I prefer security_enclave_load(), "from_memory" seems redundant at best. > > Fine with me. If we think of EADD as a way of mmap()'ing an enclave file into memory, would this security_enclave_load() be the same as security_mmap_file(source_vma->vm_file, maxperm, MAP_PRIVATE), except that the target is now EPC instead of regular pages? > > > > >> Maybe some really fancy future LSM would also want loff_t > >> source_offset, but it's probably not terribly useful. This same > >> callback would be used for EAUG. EAUG always zeroes the EPC page before making it available to an enclave. So I don't think there's anything needed to done here. > >> > >> Following up on your discussion with Cedric about sigstruct, the > >> other callback would be something like: > >> > >> int security_enclave_init(struct file *sigstruct_file); I'd still insist in using a pointer rather than a file, for reasons that we've discussed before. For those who can't recall, the major reason is that most implementation would embed SIGSTRUCT into the same file as the enclave (or at least I don't want to prevent anyone from doing so), which could also be part of another file, such as a shared object or even the main executable itself. It could be difficult to obtain a fd in those cases. memfd won't work because it can't retain the same attributes of the original file containing the SIGSTRUCT. After all, what matters is the attributes associated with the backing file, which could be easily retrieve from vm_file of the covering VMA. So for the sake of flexibility, let's stay with what we've agreed before - a pointer to SIGSTRUCT. > >> > >> The main issue I see is that we also want to control the enclave's > >> ability to have RWX pages or to change a W page to X. We might also > >> want: > >> > >> int security_enclave_load_zeros(unsigned int maxperm); > > > > What's the use case for this? @maxperm will always be at least RW in > > this case, otherwise the page is useless to the enclave, and if the > > enclave can write the page, the fact that it started as zeros is > > irrelevant. > > This is how EAUG could ask if RWX is okay. If an enclave is internally doing dynamic loading, > the it will need a heap page with maxperm = RWX. (If it’s well designed, it will make it RW > and then RX, either by changing SECINFO or by asking the host to mprotect() it, but it still > needs the overall RWX mask.). Any new page EAUG'ed will start in RW (as dictated by SGX ISA). EACCEPTCOPY will then change it to RX. RWX is never needed for all practical purposes. This in fact could be gated by mprotect() and the attributes associated with /dev/sgx/enclave. In the case of SELinux, FILE__EXECMOD is the right attribute and mprotect() will take care of all the rest. I don't see why the driver need a role here. > > Also, do real SGX1 enclave formats have BSS? If so, then either we need an ioctl or load zeros > or user code is going to load from /dev/zero or just from the heap, but the LSM is going to > play better with an ioctl, I suspect :) Yes, it does. But an enclave would either measure BSS, in which case the initial bytes have to be zero or MRENCLAVE will change; or zero BSS explicitly in its initialization code. But from LSM's perspective it makes no difference than EADD'ing a page with non-zero content. And security_enclave_load(NULL, RW) would take care of it in exactly in the same way. > > > > >> An enclave that's going to modify its own code will need memory with > >> maxperm = RWX or WX. With SGX2/EDMM, RWX is *never* needed for all practical purposes. In theory, in terms of security, no page shall be made executable while it is still being prepared. So W and X shall always be mutually exclusive, regardless it's in EPC or regular memory. RWX is only needed in SGX1, as a workaround for certain usages, because EPCM permissions can never change at runtime. > >> > >> But this is a bit awkward if the LSM's decision depends on the > >> sigstruct. We could get fancy and require that the sigstruct be > >> supplied before any EADD operations so that the maxperm decisions can > >> depend on the sigstruct. > >> > >> Am I making more sense now? > > > > Yep. Requiring .sigstruct at ECREATE would be trivial. If we wanted > > flexibility we could do: > > > > int security_enclave_load(struct file *file, struct vm_area_struct *vma, > > unsigned long prot); > > > > And for ultimate flexibility we could pass both .sigstruct and the > > file pointer for /dev/sgx/enclave, but that seems a bit ridiculous. > > I agree. Loosely speaking, an enclave (including initial contents of all of its pages and their permissions) and its MRENCLAVE are a 1-to-1 correspondence (given the collision resistant property of SHA-2). So only one is needed for a decision, and either one would lead to the same decision. So I don't see anything making any sense here. Theoretically speaking, if LSM can make a decision at EINIT by means of security_enclave_load(), then security_enclave_load() is never needed. In practice, I support keeping both because security_enclave_load() can only approve an enumerable set while security_enclave_load() can approve a non-enumerable set of enclaves. Moreover, in order to determine the validity of a MRENCLAVE (as in development of a policy or in creation of a white/black list), system admins will need the audit log produced by security_enclave_load(). > > > > > Passing both would allow tying EXECMOD to /dev/sgx/enclave as Cedric > > wanted (without having to play games and pass /dev/sgx/enclave to > > security_enclave_load()), but I don't think there's anything > > fundamentally broken with using .sigstruct for EXECMOD. It requires > > more verbose labeling, but that's not a bad thing. > > The benefit of putting it on .sigstruct is that it can be per-enclave. > > As I understand it from Fedora packaging, the way this works on distros is generally that a > package will include some files and their associated labels, and, if the package needs EXECMOD, > then the files are labeled with EXECMOD and the author of the relevant code might get a dirty > look. > > This could translate to the author of an exclave that needs RWX regions getting a dirty look > without leaking this permission into other enclaves. > > (In my opinion, the dirty looks are actually the best security benefit of the entire concept > of LSMs making RWX difficult. A sufficiently creative attacker can almost always bypass W^X > restrictions once they’ve pwned you, but W^X makes it harder to pwn you in the first place, > and SELinux makes it really obvious when packaging a program that doesn’t respect W^X. The > upshot is that a lot of programs got fixed.) I'm lost here. Dynamically linked enclaves, if running on SGX2, would need RW->RX, i.e. FILE__EXECMOD on /dev/sgx/enclave. But they never need RWX, i.e. PROCESS__EXECMEM.
On Sat, May 25, 2019 at 3:40 PM Xing, Cedric <cedric.xing@intel.com> wrote: > > > From: Andy Lutomirski [mailto:luto@amacapital.net] > > Sent: Friday, May 24, 2019 4:42 PM > > > > > On May 24, 2019, at 3:41 PM, Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > > > > >> On Fri, May 24, 2019 at 02:27:34PM -0700, Andy Lutomirski wrote: > > >> On Fri, May 24, 2019 at 1:03 PM Sean Christopherson > > >> <sean.j.christopherson@intel.com> wrote: > > >>> > > >>>> On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote: > > >>>>> On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> wrote: > > >>>>> > > >>>>> If "initial permissions" for enclaves are less restrictive than > > >>>>> shared objects, then it'd become a backdoor for circumventing LSM > > >>>>> when enclave whitelisting is *not* in place. For example, an > > >>>>> adversary may load a page, which would otherwise never be executable, as an executable > > page in EPC. > > >>>>> > > >>>>> In the case a RWX page is needed, the calling process has to have > > >>>>> a RWX page serving as the source for EADD so PROCESS__EXECMEM will > > >>>>> have been checked. For SGX2, changing an EPC page to RWX is > > >>>>> subject to FILE__EXECMEM on /dev/sgx/enclave, which I see as a > > >>>>> security benefit because it only affects the enclave but not the whole process hosting > > it. > > >>>> > > >>>> So the permission would be like FILE__EXECMOD on the source enclave > > >>>> page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE? > > >>>> MAP_SHARED, PROT_WRITE isn't going to work because that means you > > >>>> can modify the file. > > >>> > > >>> Was this in response to Cedric's comment, or to my comment? > > >> > > >> Yours. I think that requiring source pages to be actually mapped W > > >> is not such a great idea. > > > > > > I wasn't requiring source pages to be mapped W. At least I didn't > > > intend to require W. What I was trying to say is that SGX could > > > trigger an EXECMEM check if userspace attempted to EADD or EAUG an > > > enclave page with RWX permissions, e.g.: > > > > > > if ((SECINFO.PERMS & RWX) == RWX) { > > > ret = security_mmap_file(NULL, RWX, ???); > > > if (ret) > > > return ret; > > > } > > > > > > But that's a moot point if we add security_enclave_load() or whatever. > > > > > >> > > >>> > > >>>> I'm starting to think that looking at the source VMA permission > > >>>> bits or source PTE permission bits is putting a bit too much policy > > >>>> into the driver as opposed to the LSM. How about delegating the > > >>>> whole thing to an LSM hook? The EADD operation would invoke a new > > >>>> hook, something like: > > >>>> > > >>>> int security_enclave_load_bytes(void *source_addr, struct > > >>>> vm_area_struct *source_vma, loff_t source_offset, unsigned int > > >>>> maxperm); > > >>>> > > >>>> Then you don't have to muck with mapping anything PROT_EXEC. > > >>>> Instead you load from a mapping of a file and the LSM applies > > >>>> whatever policy it feels appropriate. If the first pass gets > > >>>> something wrong, the application or library authors can take it up > > >>>> with the SELinux folks without breaking the whole ABI :) > > >>>> > > >>>> (I'm proposing passing in the source_vma because this hook would be > > >>>> called with mmap_sem held for read to avoid a TOCTOU race.) > > >>>> > > >>>> If we go this route, the only substantial change to the existing > > >>>> driver that's needed for an initial upstream merge is the maxperm > > >>>> mechanism and whatever hopefully minimal API changes are needed to > > >>>> allow users to conveniently set up the mappings. And we don't need > > >>>> to worry about how to hack around mprotect() calling into the LSM, > > >>>> because the LSM will actually be aware of SGX and can just do the > > >>>> right thing. > > >>> > > >>> This doesn't address restricting which processes can run which > > >>> enclaves, it only allows restricting the build flow. Or are you > > >>> suggesting this be done in addition to whitelisting sigstructs? > > >> > > >> In addition. > > >> > > >> But I named the function badly and gave it a bad signature, which > > >> confused you. Let's try again: > > >> > > >> int security_enclave_load_from_memory(const struct vm_area_struct > > >> *source, unsigned int maxperm); > > > > > > I prefer security_enclave_load(), "from_memory" seems redundant at best. > > > > Fine with me. > > If we think of EADD as a way of mmap()'ing an enclave file into memory, would this security_enclave_load() be the same as security_mmap_file(source_vma->vm_file, maxperm, MAP_PRIVATE), except that the target is now EPC instead of regular pages? Hmm, that's clever. Although it seems plausible that an LSM would want to allow RX or RWX of a given file page but only in the context of an approved enclave, so I think it should still be its own hook. > > > > > > > > >> Maybe some really fancy future LSM would also want loff_t > > >> source_offset, but it's probably not terribly useful. This same > > >> callback would be used for EAUG. > > EAUG always zeroes the EPC page before making it available to an enclave. So I don't think there's anything needed to done here. Duh. So security_enclave_load_zeros() for EAUG. See below. > > > >> > > >> Following up on your discussion with Cedric about sigstruct, the > > >> other callback would be something like: > > >> > > >> int security_enclave_init(struct file *sigstruct_file); > > I'd still insist in using a pointer rather than a file, for reasons that we've discussed before. For those who can't recall, the major reason is that most implementation would embed SIGSTRUCT into the same file as the enclave (or at least I don't want to prevent anyone from doing so), which could also be part of another file, such as a shared object or even the main executable itself. It could be difficult to obtain a fd in those cases. memfd won't work because it can't retain the same attributes of the original file containing the SIGSTRUCT. > > After all, what matters is the attributes associated with the backing file, which could be easily retrieve from vm_file of the covering VMA. So for the sake of flexibility, let's stay with what we've agreed before - a pointer to SIGSTRUCT. I'm okay with this, except for one nastiness: there's a big difference between a file that is just a sigstruct and a file that contains essentially arbitrary data plus a sigstruct at an arbitrary offset. We could do something tricky like saying that SIGSTRUCT can be in a file that's just a SIGSTRUCT or it can be in a special SIGSTRUCT ELF note in a file that isn't just a SIGSTRUCT, but that could be annoyingly restrictive. If it's going to be in an arbitrary file, then I think the signature needs to be more like: int security_enclave_init(struct vm_area_struct *sigstruct_vma, loff_t sigstruct_offset, const sgx_sigstruct *sigstruct); So that the LSM still has the opportunity to base its decision on the contents of the SIGSTRUCT. Actually, we need that change regardless. > > > >> > > >> The main issue I see is that we also want to control the enclave's > > >> ability to have RWX pages or to change a W page to X. We might also > > >> want: > > >> > > >> int security_enclave_load_zeros(unsigned int maxperm); > > > > > > What's the use case for this? @maxperm will always be at least RW in > > > this case, otherwise the page is useless to the enclave, and if the > > > enclave can write the page, the fact that it started as zeros is > > > irrelevant. > > > > This is how EAUG could ask if RWX is okay. If an enclave is internally doing dynamic loading, > > the it will need a heap page with maxperm = RWX. (If it’s well designed, it will make it RW > > and then RX, either by changing SECINFO or by asking the host to mprotect() it, but it still > > needs the overall RWX mask.). > > Any new page EAUG'ed will start in RW (as dictated by SGX ISA). EACCEPTCOPY will then change it to RX. RWX is never needed for all practical purposes. This in fact could be gated by mprotect() and the attributes associated with /dev/sgx/enclave. In the case of SELinux, FILE__EXECMOD is the right attribute and mprotect() will take care of all the rest. I don't see why the driver need a role here. I find the SDM's discussion of EAUG, EACCEPT, and EACCEPTCOPY to be extremely confusing. My copy of the SDM has EACCEPT's SECINFO argument as "Read access permitted by Non Enclave". Is that an error? And is EACCEPTCOPY just EACCEPT + memcpy or is there some other fundamental difference? 38.5.7 doesn't even mention EACCEPTCOPY. Anyway, all my confusion aside, I was talking about the page table, not the EPCM. I think the enclave should need permission to write its own content into a page that will ever become X, and the enclave's untrusted host library would do this by adding the page with MAXPERM=RWX and then mapping/mprotecting it as PROT_WRITE and then (later or simultaneously) PROT_EXEC. Since SGX2 doesn't seem to have a way to add an initialized page to EPC after an enclave starts, I guess that it's impossible to have the enclave do something like dlopen() without MAXPERM=RWX. So be it. Maybe someone will find this annoying someday and SGX3 will add EAUG-but-don't-zero and EACCEPT-with-existing-contents. > > > > > Also, do real SGX1 enclave formats have BSS? If so, then either we need an ioctl or load zeros > > or user code is going to load from /dev/zero or just from the heap, but the LSM is going to > > play better with an ioctl, I suspect :) > > Yes, it does. But an enclave would either measure BSS, in which case the initial bytes have to be zero or MRENCLAVE will change; or zero BSS explicitly in its initialization code. > > But from LSM's perspective it makes no difference than EADD'ing a page with non-zero content. And security_enclave_load(NULL, RW) would take care of it in exactly in the same way. Sure, I suppose the same hook with NULL parameters would be equivalent. > > > > > > > > >> An enclave that's going to modify its own code will need memory with > > >> maxperm = RWX or WX. > > With SGX2/EDMM, RWX is *never* needed for all practical purposes. > > In theory, in terms of security, no page shall be made executable while it is still being prepared. So W and X shall always be mutually exclusive, regardless it's in EPC or regular memory. > > RWX is only needed in SGX1, as a workaround for certain usages, because EPCM permissions can never change at runtime. As above, I think I disagree. MAXPERM is intended as an upper bound on the permissions that a page can ever have, at least until it's EREMOVEd and re-added. Since there's no EAUG-but-don't-zero, EAUG with MAXPERM.W=0 is basically useless because the page can never contain anything other than zeros, so a dynamically allocated page that is ever executed has to have MAXPERM=RWX or MAXPERM=WX. And that will need special permissions, which I think is consistent with your recent emails on how this could all map to SELinux permissions. > > > >> > > >> But this is a bit awkward if the LSM's decision depends on the > > >> sigstruct. We could get fancy and require that the sigstruct be > > >> supplied before any EADD operations so that the maxperm decisions can > > >> depend on the sigstruct. > > >> > > >> Am I making more sense now? > > > > > > Yep. Requiring .sigstruct at ECREATE would be trivial. If we wanted > > > flexibility we could do: > > > > > > int security_enclave_load(struct file *file, struct vm_area_struct *vma, > > > unsigned long prot); > > > > > > And for ultimate flexibility we could pass both .sigstruct and the > > > file pointer for /dev/sgx/enclave, but that seems a bit ridiculous. > > > > I agree. > > Loosely speaking, an enclave (including initial contents of all of its pages and their permissions) and its MRENCLAVE are a 1-to-1 correspondence (given the collision resistant property of SHA-2). So only one is needed for a decision, and either one would lead to the same decision. So I don't see anything making any sense here. > > Theoretically speaking, if LSM can make a decision at EINIT by means of security_enclave_load(), then security_enclave_load() is never needed. > > In practice, I support keeping both because security_enclave_load() can only approve an enumerable set while security_enclave_load() can approve a non-enumerable set of enclaves. Moreover, in order to determine the validity of a MRENCLAVE (as in development of a policy or in creation of a white/black list), system admins will need the audit log produced by security_enclave_load(). I'm confused. Things like MRSIGNER aren't known until the SIGSTRUCT shows up. Also, security_enclave_load() provides no protection against loading a mishmash of two different enclave files. I see security_enclave_init() as "verify this SIGSTRUCT against your policy on who may sign enclaves and/or grant EXECMOD depending on SIGSTRUCT" and security_enclave_load() as "implement your EXECMOD / EXECUTE / WRITE / whatever policy and possibly check enclave files for some label." > > > > > > > > > Passing both would allow tying EXECMOD to /dev/sgx/enclave as Cedric > > > wanted (without having to play games and pass /dev/sgx/enclave to > > > security_enclave_load()), but I don't think there's anything > > > fundamentally broken with using .sigstruct for EXECMOD. It requires > > > more verbose labeling, but that's not a bad thing. > > > > The benefit of putting it on .sigstruct is that it can be per-enclave. > > > > As I understand it from Fedora packaging, the way this works on distros is generally that a > > package will include some files and their associated labels, and, if the package needs EXECMOD, > > then the files are labeled with EXECMOD and the author of the relevant code might get a dirty > > look. > > > > This could translate to the author of an exclave that needs RWX regions getting a dirty look > > without leaking this permission into other enclaves. > > > > (In my opinion, the dirty looks are actually the best security benefit of the entire concept > > of LSMs making RWX difficult. A sufficiently creative attacker can almost always bypass W^X > > restrictions once they’ve pwned you, but W^X makes it harder to pwn you in the first place, > > and SELinux makes it really obvious when packaging a program that doesn’t respect W^X. The > > upshot is that a lot of programs got fixed.) > > I'm lost here. Dynamically linked enclaves, if running on SGX2, would need RW->RX, i.e. FILE__EXECMOD on /dev/sgx/enclave. But they never need RWX, i.e. PROCESS__EXECMEM. Hmm. If we want to make this distinction, we need something a big richer than my proposed callbacks. A check of the actual mprotect() / mmap() permissions would also be needed. Specifically, allowing MAXPERM=RWX wouldn't imply that PROT_WRITE | PROT_EXEC is allowed.
> From: Andy Lutomirski [mailto:luto@kernel.org] > Sent: Saturday, May 25, 2019 5:58 PM > > On Sat, May 25, 2019 at 3:40 PM Xing, Cedric <cedric.xing@intel.com> wrote: > > > > > From: Andy Lutomirski [mailto:luto@amacapital.net] > > > Sent: Friday, May 24, 2019 4:42 PM > > > > > > > On May 24, 2019, at 3:41 PM, Sean Christopherson <sean.j.christopherson@intel.com> > wrote: > > > > > > > >> On Fri, May 24, 2019 at 02:27:34PM -0700, Andy Lutomirski wrote: > > > >> On Fri, May 24, 2019 at 1:03 PM Sean Christopherson > > > >> <sean.j.christopherson@intel.com> wrote: > > > >>> > > > >>>> On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote: > > > >>>>> On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> wrote: > > > >>>>> > > > >>>>> If "initial permissions" for enclaves are less restrictive > > > >>>>> than shared objects, then it'd become a backdoor for > > > >>>>> circumventing LSM when enclave whitelisting is *not* in place. > > > >>>>> For example, an adversary may load a page, which would > > > >>>>> otherwise never be executable, as an executable > > > page in EPC. > > > >>>>> > > > >>>>> In the case a RWX page is needed, the calling process has to > > > >>>>> have a RWX page serving as the source for EADD so > > > >>>>> PROCESS__EXECMEM will have been checked. For SGX2, changing an > > > >>>>> EPC page to RWX is subject to FILE__EXECMEM on > > > >>>>> /dev/sgx/enclave, which I see as a security benefit because it > > > >>>>> only affects the enclave but not the whole process hosting > > > it. > > > >>>> > > > >>>> So the permission would be like FILE__EXECMOD on the source > > > >>>> enclave page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE? > > > >>>> MAP_SHARED, PROT_WRITE isn't going to work because that means > > > >>>> you can modify the file. > > > >>> > > > >>> Was this in response to Cedric's comment, or to my comment? > > > >> > > > >> Yours. I think that requiring source pages to be actually mapped > > > >> W is not such a great idea. > > > > > > > > I wasn't requiring source pages to be mapped W. At least I didn't > > > > intend to require W. What I was trying to say is that SGX could > > > > trigger an EXECMEM check if userspace attempted to EADD or EAUG an > > > > enclave page with RWX permissions, e.g.: > > > > > > > > if ((SECINFO.PERMS & RWX) == RWX) { > > > > ret = security_mmap_file(NULL, RWX, ???); > > > > if (ret) > > > > return ret; > > > > } > > > > > > > > But that's a moot point if we add security_enclave_load() or whatever. > > > > > > > >> > > > >>> > > > >>>> I'm starting to think that looking at the source VMA permission > > > >>>> bits or source PTE permission bits is putting a bit too much > > > >>>> policy into the driver as opposed to the LSM. How about > > > >>>> delegating the whole thing to an LSM hook? The EADD operation > > > >>>> would invoke a new hook, something like: > > > >>>> > > > >>>> int security_enclave_load_bytes(void *source_addr, struct > > > >>>> vm_area_struct *source_vma, loff_t source_offset, unsigned int > > > >>>> maxperm); > > > >>>> > > > >>>> Then you don't have to muck with mapping anything PROT_EXEC. > > > >>>> Instead you load from a mapping of a file and the LSM applies > > > >>>> whatever policy it feels appropriate. If the first pass gets > > > >>>> something wrong, the application or library authors can take it > > > >>>> up with the SELinux folks without breaking the whole ABI :) > > > >>>> > > > >>>> (I'm proposing passing in the source_vma because this hook > > > >>>> would be called with mmap_sem held for read to avoid a TOCTOU > > > >>>> race.) > > > >>>> > > > >>>> If we go this route, the only substantial change to the > > > >>>> existing driver that's needed for an initial upstream merge is > > > >>>> the maxperm mechanism and whatever hopefully minimal API > > > >>>> changes are needed to allow users to conveniently set up the > > > >>>> mappings. And we don't need to worry about how to hack around > > > >>>> mprotect() calling into the LSM, because the LSM will actually > > > >>>> be aware of SGX and can just do the right thing. > > > >>> > > > >>> This doesn't address restricting which processes can run which > > > >>> enclaves, it only allows restricting the build flow. Or are you > > > >>> suggesting this be done in addition to whitelisting sigstructs? > > > >> > > > >> In addition. > > > >> > > > >> But I named the function badly and gave it a bad signature, which > > > >> confused you. Let's try again: > > > >> > > > >> int security_enclave_load_from_memory(const struct vm_area_struct > > > >> *source, unsigned int maxperm); > > > > > > > > I prefer security_enclave_load(), "from_memory" seems redundant at best. > > > > > > Fine with me. > > > > If we think of EADD as a way of mmap()'ing an enclave file into memory, would this > security_enclave_load() be the same as security_mmap_file(source_vma->vm_file, maxperm, > MAP_PRIVATE), except that the target is now EPC instead of regular pages? > > Hmm, that's clever. Although it seems plausible that an LSM would want to allow RX or RWX > of a given file page but only in the context of an approved enclave, so I think it should > still be its own hook. What do you mean by "in the context of an approved enclave"? EPC pages are *inaccessible* to any software until after EINIT. So it would never be a security concern to EADD a page with wrong permissions as long as the enclave would be denied eventually by LSM at EINIT. But I acknowledge the difference between loading a page into regular memory vs. into EPC. So it's beneficial to have a separate hook, which if not hooked, would pass through to security_mmap_file() by default? > > > > > > > > > > > > > >> Maybe some really fancy future LSM would also want loff_t > > > >> source_offset, but it's probably not terribly useful. This same > > > >> callback would be used for EAUG. > > > > EAUG always zeroes the EPC page before making it available to an enclave. So I don't > think there's anything needed to done here. > > Duh. So security_enclave_load_zeros() for EAUG. See below. > > > > > > >> > > > >> Following up on your discussion with Cedric about sigstruct, the > > > >> other callback would be something like: > > > >> > > > >> int security_enclave_init(struct file *sigstruct_file); > > > > I'd still insist in using a pointer rather than a file, for reasons that we've discussed > before. For those who can't recall, the major reason is that most implementation would > embed SIGSTRUCT into the same file as the enclave (or at least I don't want to prevent > anyone from doing so), which could also be part of another file, such as a shared object > or even the main executable itself. It could be difficult to obtain a fd in those cases. > memfd won't work because it can't retain the same attributes of the original file > containing the SIGSTRUCT. > > > > After all, what matters is the attributes associated with the backing file, which could > be easily retrieve from vm_file of the covering VMA. So for the sake of flexibility, let's > stay with what we've agreed before - a pointer to SIGSTRUCT. > > I'm okay with this, except for one nastiness: there's a big difference between a file that > is just a sigstruct and a file that contains essentially arbitrary data plus a sigstruct > at an arbitrary offset. > We could do something tricky like saying that SIGSTRUCT can be in a file that's just a > SIGSTRUCT or it can be in a special SIGSTRUCT ELF note in a file that isn't just a > SIGSTRUCT, but that could be annoyingly restrictive. Agreed. Approving a file implies approving all SIGSTRUCTs within that file. But I guess it wouldn't cause practical problems. > > If it's going to be in an arbitrary file, then I think the signature needs to be more like: > > int security_enclave_init(struct vm_area_struct *sigstruct_vma, loff_t sigstruct_offset, > const sgx_sigstruct *sigstruct); > > So that the LSM still has the opportunity to base its decision on the contents of the > SIGSTRUCT. Actually, we need that change regardless. Wouldn't the pair of { sigstruct_vma, sigstruct_offset } be the same as just a pointer, because the VMA could be looked up using the pointer and the offset would then be (pointer - vma->vm_start)? > > > > > > >> > > > >> The main issue I see is that we also want to control the > > > >> enclave's ability to have RWX pages or to change a W page to X. > > > >> We might also > > > >> want: > > > >> > > > >> int security_enclave_load_zeros(unsigned int maxperm); > > > > > > > > What's the use case for this? @maxperm will always be at least RW > > > > in this case, otherwise the page is useless to the enclave, and if > > > > the enclave can write the page, the fact that it started as zeros > > > > is irrelevant. > > > > > > This is how EAUG could ask if RWX is okay. If an enclave is > > > internally doing dynamic loading, the it will need a heap page with > > > maxperm = RWX. (If it’s well designed, it will make it RW and then > > > RX, either by changing SECINFO or by asking the host to mprotect() it, but it still > needs the overall RWX mask.). > > > > Any new page EAUG'ed will start in RW (as dictated by SGX ISA). EACCEPTCOPY will then > change it to RX. RWX is never needed for all practical purposes. This in fact could be > gated by mprotect() and the attributes associated with /dev/sgx/enclave. In the case of > SELinux, FILE__EXECMOD is the right attribute and mprotect() will take care of all the > rest. I don't see why the driver need a role here. > > I find the SDM's discussion of EAUG, EACCEPT, and EACCEPTCOPY to be extremely confusing. > My copy of the SDM has EACCEPT's SECINFO argument as "Read access permitted by Non > Enclave". Is that an error? I'm confused by those descriptions too. Guess I cannot comment if that's an error or not. Anyway, per our internal documents, for EAUG, SECINFO has to be set to PT_REG|RW. For EACCEPT, SGX ISA compares supplied SECINFO with EPCM attributes and returns an error if they don't match. EACCEPTCOPY only works on pending pages (i.e. SECINFO.P must be set), and sets EPCM access permissions to whatever supplied in SECINFO. > And is EACCEPTCOPY just EACCEPT + memcpy or is there some other fundamental difference? > 38.5.7 doesn't even mention EACCEPTCOPY. 2 differences: 1) EACCEPT only *compares* but EACCEPTCOPY *sets* EPCM permissions; and 2) EACCEPTCOPY does EACCEPT+memcpy atomically. > > Anyway, all my confusion aside, I was talking about the page table, not the EPCM. I think > the enclave should need permission to write its own content into a page that will ever > become X, and the enclave's untrusted host library would do this by adding the page with > MAXPERM=RWX and then mapping/mprotecting it as PROT_WRITE and then (later or > simultaneously) PROT_EXEC. I was talking about the same thing. A code page in EPC will start in RW (both EPCM and PTE) and end in RX (both EPCM and PTE). EACCEPTCOPY takes care of EPCM, while mprotect() could take care of PTE as long as /dev/sgx/enclave has FILE__EXECMOD. I understand your intention to enclave pages to segments with different MAXPERMs. My concern is though the host process may not always have a priori knowledge on which ranges to be used as code vs. data. After all, only the weakest link matters in security so I think what a host process cares would be whether the enclave loads code dynamically, or expands its data segments only, or neither. And for that reason, I think it more "user friendly" to keep just one MAXPERM - i.e. the most permissive one. Then we could associate that with /dev/sgx/enclave so as to relieve the driver from keeping track of too many things. > > Since SGX2 doesn't seem to have a way to add an initialized page to EPC after an enclave > starts, I guess that it's impossible to have the enclave do something like dlopen() > without MAXPERM=RWX. So be it. That's true. The reason behind it is SGX doesn’t trust anything from outside. So non-predetermined contents must be measured (e.g. EADD+EEXTEND), but it's hard to measure (or attest to) dynamically added contents so we decided to allow predetermined contents (i.e. all zeros in the case of EAUG) only. > Maybe someone will find this annoying someday and SGX3 will add EAUG-but-don't-zero and > EACCEPT-with-existing-contents. From security perspective, accepting a page that is measured/hashed to XYZ is equivalent to overwriting that page with content hashed to XYZ. EACCEPTCOPY actually does the latter. The annoying part is due to the mismatch between SGX ISA and the s/w model adopted by LSM, but that has nothing to do with security. > > > > > > > > > Also, do real SGX1 enclave formats have BSS? If so, then either we > > > need an ioctl or load zeros or user code is going to load from > > > /dev/zero or just from the heap, but the LSM is going to play better > > > with an ioctl, I suspect :) > > > > Yes, it does. But an enclave would either measure BSS, in which case the initial bytes > have to be zero or MRENCLAVE will change; or zero BSS explicitly in its initialization > code. > > > > But from LSM's perspective it makes no difference than EADD'ing a page with non-zero > content. And security_enclave_load(NULL, RW) would take care of it in exactly in the same > way. > > Sure, I suppose the same hook with NULL parameters would be equivalent. > > > > > > > > > > > > > >> An enclave that's going to modify its own code will need memory > > > >> with maxperm = RWX or WX. > > > > With SGX2/EDMM, RWX is *never* needed for all practical purposes. > > > > In theory, in terms of security, no page shall be made executable while it is still > being prepared. So W and X shall always be mutually exclusive, regardless it's in EPC or > regular memory. > > > > RWX is only needed in SGX1, as a workaround for certain usages, because EPCM permissions > can never change at runtime. > > As above, I think I disagree. MAXPERM is intended as an upper bound on the permissions > that a page can ever have, at least until it's EREMOVEd and re-added. Since there's no > EAUG-but-don't-zero, EAUG with MAXPERM.W=0 is basically useless because the page can never > contain anything other than zeros, so a dynamically allocated page that is ever executed > has to have MAXPERM=RWX or MAXPERM=WX. And that will need special permissions, which I > think is consistent with your recent emails on how this could all map to SELinux > permissions. I'm totally with you. What I was trying to say was that only W or X would be needed at any given time. That said, MAXPERM=RWX but PROCESS__EXECMEM will not be needed, while FILE__EXECMOD will be needed only on /dev/sgx/enclave. So the inherent risk is contained. > > > > > > >> > > > >> But this is a bit awkward if the LSM's decision depends on the > > > >> sigstruct. We could get fancy and require that the sigstruct be > > > >> supplied before any EADD operations so that the maxperm decisions > > > >> can depend on the sigstruct. > > > >> > > > >> Am I making more sense now? > > > > > > > > Yep. Requiring .sigstruct at ECREATE would be trivial. If we > > > > wanted flexibility we could do: > > > > > > > > int security_enclave_load(struct file *file, struct vm_area_struct *vma, > > > > unsigned long prot); > > > > > > > > And for ultimate flexibility we could pass both .sigstruct and the > > > > file pointer for /dev/sgx/enclave, but that seems a bit ridiculous. > > > > > > I agree. > > > > Loosely speaking, an enclave (including initial contents of all of its pages and their > permissions) and its MRENCLAVE are a 1-to-1 correspondence (given the collision resistant > property of SHA-2). So only one is needed for a decision, and either one would lead to the > same decision. So I don't see anything making any sense here. > > > > Theoretically speaking, if LSM can make a decision at EINIT by means of > security_enclave_load(), then security_enclave_load() is never needed. > > > > In practice, I support keeping both because security_enclave_load() can only approve an > enumerable set while security_enclave_load() can approve a non-enumerable set of enclaves. > Moreover, in order to determine the validity of a MRENCLAVE (as in development of a policy > or in creation of a white/black list), system admins will need the audit log produced by > security_enclave_load(). > > I'm confused. Things like MRSIGNER aren't known until the SIGSTRUCT shows up. Also, > security_enclave_load() provides no protection against loading a mishmash of two different > enclave files. I see > security_enclave_init() as "verify this SIGSTRUCT against your policy on who may sign > enclaves and/or grant EXECMOD depending on SIGSTRUCT" > and security_enclave_load() as "implement your EXECMOD / EXECUTE / WRITE / whatever policy > and possibly check enclave files for some label." Sorry for the confusion. I was saying the same thing except that the decision of security_enclave_load() doesn't have to depend on SIGSTRUCT. Given your prototype of security_enclave_load(), I think we are on the same page. I made the above comment to object to the idea of "require that the sigstruct be supplied before any EADD operations so that the maxperm decisions can depend on the sigstruct". > > > > > > > > > > > > > > Passing both would allow tying EXECMOD to /dev/sgx/enclave as > > > > Cedric wanted (without having to play games and pass > > > > /dev/sgx/enclave to security_enclave_load()), but I don't think > > > > there's anything fundamentally broken with using .sigstruct for > > > > EXECMOD. It requires more verbose labeling, but that's not a bad thing. > > > > > > The benefit of putting it on .sigstruct is that it can be per-enclave. > > > > > > As I understand it from Fedora packaging, the way this works on > > > distros is generally that a package will include some files and > > > their associated labels, and, if the package needs EXECMOD, then the > > > files are labeled with EXECMOD and the author of the relevant code might get a dirty > look. > > > > > > This could translate to the author of an exclave that needs RWX > > > regions getting a dirty look without leaking this permission into other enclaves. > > > > > > (In my opinion, the dirty looks are actually the best security > > > benefit of the entire concept of LSMs making RWX difficult. A > > > sufficiently creative attacker can almost always bypass W^X > > > restrictions once they’ve pwned you, but W^X makes it harder to pwn > > > you in the first place, and SELinux makes it really obvious when > > > packaging a program that doesn’t respect W^X. The upshot is that a > > > lot of programs got fixed.) > > > > I'm lost here. Dynamically linked enclaves, if running on SGX2, would need RW->RX, i.e. > FILE__EXECMOD on /dev/sgx/enclave. But they never need RWX, i.e. PROCESS__EXECMEM. > > Hmm. If we want to make this distinction, we need something a big richer than my proposed > callbacks. A check of the actual mprotect() / > mmap() permissions would also be needed. Specifically, allowing MAXPERM=RWX wouldn't > imply that PROT_WRITE | PROT_EXEC is allowed. If we keep only one MAXPERM, wouldn't this be the current behavior of mmap()/mprotect()? To be a bit more clear, system admin sets MAXPERM upper bound in the form of FILE__{READ|WRITE|EXECUTE|EXECMOD} of /dev/sgx/enclave. Then for a process/enclave, if what it requires falls below what's allowed on /dev/sgx/enclave, then everything will just work. Otherwise, it fails in the form of -EPERM returned from mmap()/mprotect(). Please note that MAXPERM here applies to "runtime" permissions, while "initial" permissions are taken care of by security_enclave_{load|init}. "initial" permissions could be more permissive than "runtime" permissions, e.g., RX is still required for initial code pages even though system admins could disable dynamically loaded code pages by *not* giving FILE__{EXECUTE|EXECMOD}. Therefore, the "initial" mapping would still have to be done by the driver (to bypass LSM), either via a new ioctl or as part of IOC_EINIT.
On Thu, May 23, 2019 at 07:17:52AM -0700, Sean Christopherson wrote: > 1. Do nothing. Userspace would essentially be required to mmap() the > enclave after EINIT, which is ugly but not breaking since userspace > could mmap() the enclave with a placeholder VMA prior to building > the enclave, and then a series of mmap() to establish its "real" > mapping. What it'd break to return error if mmap() is done before EINIT? > 2. Propagate the permissions from EADD to the VMAs of the current mm > if the entire EADD range is mapped and the mapping is PROT_NONE. Right now you can do multiple mmap's. If the mmap's must be done after EINIT, the driver could check that permissions match the permissions in that range. This leaves open how to deal with mprotect() but if the process does not have FILE__WRITE I guess you cannot do much. > 3. Propagate the permissions from EADD to the VMAs of all mm structs > that have mapped some piece of the enclave, following the matching > rules from #2. For me it looks that allowing mmap's only after EINIT would result the least confusing implemntation. /Jarkko
On Mon, May 27, 2019 at 04:34:31PM +0300, Jarkko Sakkinen wrote: > On Thu, May 23, 2019 at 07:17:52AM -0700, Sean Christopherson wrote: > > 1. Do nothing. Userspace would essentially be required to mmap() the > > enclave after EINIT, which is ugly but not breaking since userspace > > could mmap() the enclave with a placeholder VMA prior to building > > the enclave, and then a series of mmap() to establish its "real" > > mapping. > > What it'd break to return error if mmap() is done before EINIT? > > > 2. Propagate the permissions from EADD to the VMAs of the current mm > > if the entire EADD range is mapped and the mapping is PROT_NONE. > > Right now you can do multiple mmap's. If the mmap's must be done after > EINIT, the driver could check that permissions match the permissions in > that range. > > This leaves open how to deal with mprotect() but if the process does not > have FILE__WRITE I guess you cannot do much. > > > 3. Propagate the permissions from EADD to the VMAs of all mm structs > > that have mapped some piece of the enclave, following the matching > > rules from #2. > > For me it looks that allowing mmap's only after EINIT would result the > least confusing implemntation. Obvious problem is of course the requirement of fixed mapping, which is of course nasty. /Jarkko
On Thu, May 23, 2019 at 08:38:17AM -0700, Andy Lutomirski wrote: > On Thu, May 23, 2019 at 7:17 AM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote: > > > On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote: > > > > But actually, there's no need to disallow mmap() after ECREATE since the > > > > LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to > > > > mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap() > > > > bypassed LSM checks? The real problem is that mmap()'ng an existing > > > > enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back > > > > at square one. > > > > > > I'm lost with the constraints we want to set. > > > > As is today, SELinux policies would require enclave loaders to have > > FILE__WRITE and FILE__EXECUTE permissions on /dev/sgx/enclave. Presumably > > other LSMs have similar requirements. Requiring all processes to have > > FILE__{WRITE,EXECUTE} permissions means the permissions don't add much > > value, e.g. they can't be used to distinguish between an enclave that is > > being loaded from an unmodified file and an enclave that is being > > generated on the fly, e.g. Graphene. > > > > Looking back at Andy's mail, he was talking about requiring FILE__EXECUTE > > to run an enclave, so perhaps it's only FILE__WRITE that we're trying to > > special case. > > > > I thought about this some more, and I have a new proposal that helps > address the ELRANGE alignment issue and the permission issue at the > cost of some extra verbosity. Maybe you all can poke holes in it :) > The basic idea is to make everything more explicit from a user's > perspective. Here's how it works: > > Opening /dev/sgx/enclave gives an enclave_fd that, by design, doesn't > give EXECUTE or WRITE. mmap() on the enclave_fd only works if you > pass PROT_NONE and gives the correct alignment. The resulting VMA > cannot be mprotected or mremapped. It can't be mmapped at all until > after ECREATE because the alignment isn't known before that. How to deny mprotect()? struct file_operations does not have callback for that (AFAIK). > Associated with the enclave are a bunch (up to 7) "enclave segment > inodes". These are anon_inodes that are created automagically. An > enclave segment is a group of pages, not necessary contiguous, with an > upper bound on the memory permissions. Each enclave page belongs to a > segment. When you do EADD, you tell the driver what segment you're > adding to. [0] This means that EADD gets an extra argument that is a > permission mask for the page -- in addition to the initial SECINFO, > you also pass to EADD something to the effect of "I promise never to > map this with permissions greater than RX". > > Then we just need some way to mmap a region from an enclave segment. > This could be done by having a way to get an fd for an enclave segment > or it could be done by having a new ioctl SGX_IOC_MAP_SEGMENT. User > code would use this operation to replace, MAP_FIXED-style, ranges from > the big PROT_NONE mapping with the relevant pages from the enclave > segment. The resulting vma would only have VM_MAYWRITE if the segment > is W, only have VM_MAYEXEC if the segment is X, and only have > VM_MAYREAD if the segment is R. Depending on implementation details, > the VMAs might need to restrict mremap() to avoid mapping pages that > aren't part of the segment in question. > > It's plausible that this whole thing works without the magic segment > inodes under the hood, but figuring that out would need a careful look > at how all the core mm bits and LSM bits work together. > > To get all the LSM stuff to work, SELinux will need some way to > automatically assign an appropriate label to the segment inodes. I > assume that such a mechanism already exists and gets used for things > like sockets, but I haven't actually confirmed this. > > [0] There needs to be some vaguely intelligent semantics if you EADD > the *same* address more than once. A simple solution would be to > disallow it if the segments don't match. What if instead simply: - Require to do PROT_NONE mmap() for the ELRANGE before ECREATE. - Disallow mprotect() up until EINIT. - Given that we have a callback for mprotect() check that permissions match EADD'd permissions. /Jarkko
On Sat, May 25, 2019 at 11:09:38PM -0700, Xing, Cedric wrote: > > From: Andy Lutomirski [mailto:luto@kernel.org] > > Sent: Saturday, May 25, 2019 5:58 PM > > > > On Sat, May 25, 2019 at 3:40 PM Xing, Cedric <cedric.xing@intel.com> wrote: > > > > > > If we think of EADD as a way of mmap()'ing an enclave file into memory, > > > would this > > security_enclave_load() be the same as > > security_mmap_file(source_vma->vm_file, maxperm, MAP_PRIVATE), except that > > the target is now EPC instead of regular pages? > > > > Hmm, that's clever. Although it seems plausible that an LSM would want to > > allow RX or RWX of a given file page but only in the context of an approved > > enclave, so I think it should still be its own hook. > > What do you mean by "in the context of an approved enclave"? EPC pages are > *inaccessible* to any software until after EINIT. So it would never be a > security concern to EADD a page with wrong permissions as long as the enclave > would be denied eventually by LSM at EINIT. > > But I acknowledge the difference between loading a page into regular memory > vs. into EPC. So it's beneficial to have a separate hook, which if not > hooked, would pass through to security_mmap_file() by default? Mapping the enclave will still go through security_mmap_file(), the extra security_enclave_load() hook allows the mmap() to use PROT_NONE. > > If it's going to be in an arbitrary file, then I think the signature needs to be more like: > > > > int security_enclave_init(struct vm_area_struct *sigstruct_vma, loff_t sigstruct_offset, > > const sgx_sigstruct *sigstruct); > > > > So that the LSM still has the opportunity to base its decision on the contents of the > > SIGSTRUCT. Actually, we need that change regardless. > > Wouldn't the pair of { sigstruct_vma, sigstruct_offset } be the same as just > a pointer, because the VMA could be looked up using the pointer and the > offset would then be (pointer - vma->vm_start)? VMA has vm_file, e.g. the .sigstruct file labeled by LSMs. That being said, why does the LSM need the VMA? E.g. why not this? int security_enclave_init(struct file *file, struct sgx_sigstruct *sigstruct); > > > Loosely speaking, an enclave (including initial contents of all of its pages and their > > permissions) and its MRENCLAVE are a 1-to-1 correspondence (given the collision resistant > > property of SHA-2). So only one is needed for a decision, and either one would lead to the > > same decision. So I don't see anything making any sense here. > > > > > > Theoretically speaking, if LSM can make a decision at EINIT by means of > > security_enclave_load(), then security_enclave_load() is never needed. > > > > > > In practice, I support keeping both because security_enclave_load() can only approve an > > enumerable set while security_enclave_load() can approve a non-enumerable set of enclaves. > > Moreover, in order to determine the validity of a MRENCLAVE (as in development of a policy > > or in creation of a white/black list), system admins will need the audit log produced by > > security_enclave_load(). > > > > I'm confused. Things like MRSIGNER aren't known until the SIGSTRUCT shows > > up. Also, security_enclave_load() provides no protection against loading a > > mishmash of two different enclave files. I see security_enclave_init() as > > "verify this SIGSTRUCT against your policy on who may sign enclaves and/or > > grant EXECMOD depending on SIGSTRUCT" and security_enclave_load() as > > "implement your EXECMOD / EXECUTE / WRITE / whatever policy and possibly > > check enclave files for some label." > > Sorry for the confusion. I was saying the same thing except that the decision > of security_enclave_load() doesn't have to depend on SIGSTRUCT. Given your > prototype of security_enclave_load(), I think we are on the same page. I made > the above comment to object to the idea of "require that the sigstruct be > supplied before any EADD operations so that the maxperm decisions can depend > on the sigstruct". Except that having the sigstruct allows using the sigstruct as the proxy for the enclave. I think the last big disconnect is that Andy and I want to tie everything to an enclave-specific file, i.e. sigstruct, while you are proposing labeling /dev/sgx/enclave. If someone wants to cram several sigstructs into a single file, so be it, but using /dev/sgx/enclave means users can't do per-enclave permissions, period. What is your objection to working on the sigstruct? > > > > > Passing both would allow tying EXECMOD to /dev/sgx/enclave as > > > > > Cedric wanted (without having to play games and pass > > > > > /dev/sgx/enclave to security_enclave_load()), but I don't think > > > > > there's anything fundamentally broken with using .sigstruct for > > > > > EXECMOD. It requires more verbose labeling, but that's not a bad thing. > > > > > > > > The benefit of putting it on .sigstruct is that it can be per-enclave. > > > > > > > > As I understand it from Fedora packaging, the way this works on > > > > distros is generally that a package will include some files and > > > > their associated labels, and, if the package needs EXECMOD, then the > > > > files are labeled with EXECMOD and the author of the relevant code might get a dirty > > look. > > > > > > > > This could translate to the author of an exclave that needs RWX > > > > regions getting a dirty look without leaking this permission into other enclaves. > > > > > > > > (In my opinion, the dirty looks are actually the best security > > > > benefit of the entire concept of LSMs making RWX difficult. A > > > > sufficiently creative attacker can almost always bypass W^X > > > > restrictions once they’ve pwned you, but W^X makes it harder to pwn > > > > you in the first place, and SELinux makes it really obvious when > > > > packaging a program that doesn’t respect W^X. The upshot is that a > > > > lot of programs got fixed.) > > > > > > I'm lost here. Dynamically linked enclaves, if running on SGX2, would need RW->RX, i.e. > > FILE__EXECMOD on /dev/sgx/enclave. But they never need RWX, i.e. PROCESS__EXECMEM. > > > > Hmm. If we want to make this distinction, we need something a big richer > > than my proposed callbacks. A check of the actual mprotect() / mmap() > > permissions would also be needed. Specifically, allowing MAXPERM=RWX > > wouldn't imply that PROT_WRITE | PROT_EXEC is allowed. Actually, I think we do have everything we need from an LSM perspective. LSMs just need to understand that sgx_enclave_load() with a NULL vma implies a transition from RW. For example, SELinux would interpret sgx_enclave_load(NULL, RX) as requiring FILE__EXECMOD. As Cedric mentioned earlier, the host process doesn't necessarily know which pages will end up RW vs RX, i.e. sgx_enclave_load(NULL, RX) already has to be invoked at runtime, and when that happens, the kernel can take the opportunity to change the VMAs from MAY_RW to MAY_RX. For simplicity in the kernel and clarity in userspace, it makes sense to require an explicit ioctl() to add the to-be-EAUG'd range. That just leaves us wanting an ioctl() to set the post-EACCEPT{COPY} permissions. E.g.: ioctl(<prefix>_ADD_REGION, { NULL }) /* NULL == EAUG, MAY_RW */ mprotect(addr, size, RW); ... EACCEPTCOPY -> EAUG /* page fault handler */ ioctl(<prefix>_ACTIVATE_REGION, { addr, size, RX}) /* MAY_RX */ mprotect(addr, size, RX); ... And making ACTIVATE_REGION a single-shot per page eliminates the need for the MAXPERMS concept (see below). > If we keep only one MAXPERM, wouldn't this be the current behavior of > mmap()/mprotect()? > > To be a bit more clear, system admin sets MAXPERM upper bound in the form of > FILE__{READ|WRITE|EXECUTE|EXECMOD} of /dev/sgx/enclave. Then for a > process/enclave, if what it requires falls below what's allowed on > /dev/sgx/enclave, then everything will just work. Otherwise, it fails in the > form of -EPERM returned from mmap()/mprotect(). Please note that MAXPERM here > applies to "runtime" permissions, while "initial" permissions are taken care > of by security_enclave_{load|init}. "initial" permissions could be more > permissive than "runtime" permissions, e.g., RX is still required for initial > code pages even though system admins could disable dynamically loaded code > pages by *not* giving FILE__{EXECUTE|EXECMOD}. Therefore, the "initial" > mapping would still have to be done by the driver (to bypass LSM), either via > a new ioctl or as part of IOC_EINIT. Aha! Starting with Cedric's assertion that initial permissions can be taken directly from SECINFO: - Initial permissions for *EADD* pages are explicitly handled via sgx_enclave_load() with the exact SECINFO permissions. - Initial permissions for *EAUG* are unconditionally RW. EACCEPTCOPY requires the target EPC page to be RW, and EACCEPT with RO is useless. - Runtime permissions break down as follows: R - N/A, subset of RW (EAUG) W - N/A, subset of RW (EAUG) and x86 paging can't do W X - N/A, subset of RX (x86 paging can't do XO) RW - Handled by EAUG LSM hook (uses RW unconditionally) WX - N/A, subset of RWX (x86 paging can't do WX) RX - Handled by ACTIVATE_REGION RWX - Handled by ACTIVATE_REGION In other words, if we define the SGX -> LSM calls as follows (minus the file pointer and other params for brevity): - <prefix>_ACTIVATE_REGION(vma, perms) -> sgx_enclave_load(NULL, perms) - <prefix>_ADD_REGION(vma) -> sgx_enclave_load(vma, SECINFO.perms) - <prefix>_ADD_REGION(NULL) -> sgx_enclave_load(NULL, RW) then SGX and LSMs have all the information and hooks needed. The catch is that the LSM semantics of sgx_enclave_load(..., RW) would need to be different than normal shared memory, e.g. FILE__WRITE should *not* be required, but that's ok since it's an SGX specific hook. And if for some reason an LSM wanted to gate access to EAUG *without* FILE__EXECMOD, it'd have the necessary information to do so. The userspace changes are fairly minimal: - For SGX1, use PROT_NONE for the initial mmap() and refactor ADD_PAGE to ADD_REGION. - For SGX2, do an explicit ADD_REGION on the ranges to be EAUG'd, and an ACTIVATE_REGION to make a region RX or R (no extra ioctl() required to keep RW permissions). Because ACTIVATE_REGION can only be done once per page, to do *abitrary* mprotect() transitions, userspace would need to set the added/activated permissions to be a superset of the transitions, e.g. RW -> RX would require RWX, but that's a non-issue. - For SGX1 it's a nop since it's impossible to change the EPCM permissions, i.e. the page would need to be RWX regardless. - For SGX2, userspace can suck it up and request RWX to do completely arbitrary transitions (working as intended), or the kernel can support trimming (removing) pages from an enclave, which would allow userspace to do "arbitrary" transitions by first removing the page.
On Tue, May 28, 2019 at 1:24 PM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Sat, May 25, 2019 at 11:09:38PM -0700, Xing, Cedric wrote: > > > From: Andy Lutomirski [mailto:luto@kernel.org] > > > Sent: Saturday, May 25, 2019 5:58 PM > > > > > > On Sat, May 25, 2019 at 3:40 PM Xing, Cedric <cedric.xing@intel.com> wrote: > > > > > > > > If we think of EADD as a way of mmap()'ing an enclave file into memory, > > > > would this > > > security_enclave_load() be the same as > > > security_mmap_file(source_vma->vm_file, maxperm, MAP_PRIVATE), except that > > > the target is now EPC instead of regular pages? > > > > > > Hmm, that's clever. Although it seems plausible that an LSM would want to > > > allow RX or RWX of a given file page but only in the context of an approved > > > enclave, so I think it should still be its own hook. > > > > What do you mean by "in the context of an approved enclave"? EPC pages are > > *inaccessible* to any software until after EINIT. So it would never be a > > security concern to EADD a page with wrong permissions as long as the enclave > > would be denied eventually by LSM at EINIT. > > > > But I acknowledge the difference between loading a page into regular memory > > vs. into EPC. So it's beneficial to have a separate hook, which if not > > hooked, would pass through to security_mmap_file() by default? > > Mapping the enclave will still go through security_mmap_file(), the extra > security_enclave_load() hook allows the mmap() to use PROT_NONE. > > > > If it's going to be in an arbitrary file, then I think the signature needs to be more like: > > > > > > int security_enclave_init(struct vm_area_struct *sigstruct_vma, loff_t sigstruct_offset, > > > const sgx_sigstruct *sigstruct); > > > > > > So that the LSM still has the opportunity to base its decision on the contents of the > > > SIGSTRUCT. Actually, we need that change regardless. > > > > Wouldn't the pair of { sigstruct_vma, sigstruct_offset } be the same as just > > a pointer, because the VMA could be looked up using the pointer and the > > offset would then be (pointer - vma->vm_start)? > > VMA has vm_file, e.g. the .sigstruct file labeled by LSMs. That being > said, why does the LSM need the VMA? E.g. why not this? > > int security_enclave_init(struct file *file, struct sgx_sigstruct *sigstruct); > > > > > Loosely speaking, an enclave (including initial contents of all of its pages and their > > > permissions) and its MRENCLAVE are a 1-to-1 correspondence (given the collision resistant > > > property of SHA-2). So only one is needed for a decision, and either one would lead to the > > > same decision. So I don't see anything making any sense here. > > > > > > > > Theoretically speaking, if LSM can make a decision at EINIT by means of > > > security_enclave_load(), then security_enclave_load() is never needed. > > > > > > > > In practice, I support keeping both because security_enclave_load() can only approve an > > > enumerable set while security_enclave_load() can approve a non-enumerable set of enclaves. > > > Moreover, in order to determine the validity of a MRENCLAVE (as in development of a policy > > > or in creation of a white/black list), system admins will need the audit log produced by > > > security_enclave_load(). > > > > > > I'm confused. Things like MRSIGNER aren't known until the SIGSTRUCT shows > > > up. Also, security_enclave_load() provides no protection against loading a > > > mishmash of two different enclave files. I see security_enclave_init() as > > > "verify this SIGSTRUCT against your policy on who may sign enclaves and/or > > > grant EXECMOD depending on SIGSTRUCT" and security_enclave_load() as > > > "implement your EXECMOD / EXECUTE / WRITE / whatever policy and possibly > > > check enclave files for some label." > > > > Sorry for the confusion. I was saying the same thing except that the decision > > of security_enclave_load() doesn't have to depend on SIGSTRUCT. Given your > > prototype of security_enclave_load(), I think we are on the same page. I made > > the above comment to object to the idea of "require that the sigstruct be > > supplied before any EADD operations so that the maxperm decisions can depend > > on the sigstruct". > > Except that having the sigstruct allows using the sigstruct as the proxy > for the enclave. I think the last big disconnect is that Andy and I want > to tie everything to an enclave-specific file, i.e. sigstruct, while you > are proposing labeling /dev/sgx/enclave. If someone wants to cram several > sigstructs into a single file, so be it, but using /dev/sgx/enclave means > users can't do per-enclave permissions, period. > > What is your objection to working on the sigstruct? > > > > > > > Passing both would allow tying EXECMOD to /dev/sgx/enclave as > > > > > > Cedric wanted (without having to play games and pass > > > > > > /dev/sgx/enclave to security_enclave_load()), but I don't think > > > > > > there's anything fundamentally broken with using .sigstruct for > > > > > > EXECMOD. It requires more verbose labeling, but that's not a bad thing. > > > > > > > > > > The benefit of putting it on .sigstruct is that it can be per-enclave. > > > > > > > > > > As I understand it from Fedora packaging, the way this works on > > > > > distros is generally that a package will include some files and > > > > > their associated labels, and, if the package needs EXECMOD, then the > > > > > files are labeled with EXECMOD and the author of the relevant code might get a dirty > > > look. > > > > > > > > > > This could translate to the author of an exclave that needs RWX > > > > > regions getting a dirty look without leaking this permission into other enclaves. > > > > > > > > > > (In my opinion, the dirty looks are actually the best security > > > > > benefit of the entire concept of LSMs making RWX difficult. A > > > > > sufficiently creative attacker can almost always bypass W^X > > > > > restrictions once they’ve pwned you, but W^X makes it harder to pwn > > > > > you in the first place, and SELinux makes it really obvious when > > > > > packaging a program that doesn’t respect W^X. The upshot is that a > > > > > lot of programs got fixed.) > > > > > > > > I'm lost here. Dynamically linked enclaves, if running on SGX2, would need RW->RX, i.e. > > > FILE__EXECMOD on /dev/sgx/enclave. But they never need RWX, i.e. PROCESS__EXECMEM. > > > > > > Hmm. If we want to make this distinction, we need something a big richer > > > than my proposed callbacks. A check of the actual mprotect() / mmap() > > > permissions would also be needed. Specifically, allowing MAXPERM=RWX > > > wouldn't imply that PROT_WRITE | PROT_EXEC is allowed. > > Actually, I think we do have everything we need from an LSM perspective. > LSMs just need to understand that sgx_enclave_load() with a NULL vma > implies a transition from RW. For example, SELinux would interpret > sgx_enclave_load(NULL, RX) as requiring FILE__EXECMOD. You lost me here. What operation triggers this callback? And wouldn't sgx_enclave_load(NULL, RX) sometimes be a transition from RO or just some fresh executable zero bytes? > > As Cedric mentioned earlier, the host process doesn't necessarily know > which pages will end up RW vs RX, i.e. sgx_enclave_load(NULL, RX) > already has to be invoked at runtime, and when that happens, the kernel > can take the opportunity to change the VMAs from MAY_RW to MAY_RX. > > For simplicity in the kernel and clarity in userspace, it makes sense to > require an explicit ioctl() to add the to-be-EAUG'd range. That just > leaves us wanting an ioctl() to set the post-EACCEPT{COPY} permissions. > > E.g.: > > ioctl(<prefix>_ADD_REGION, { NULL }) /* NULL == EAUG, MAY_RW */ > > mprotect(addr, size, RW); > ... > > EACCEPTCOPY -> EAUG /* page fault handler */ > > ioctl(<prefix>_ACTIVATE_REGION, { addr, size, RX}) /* MAY_RX */ > > mprotect(addr, size, RX); In the maxperm model, this mprotect() will fail unless MAXPERM contains RX, which could only happen if MAXPERM=RWX. So, regardless of how it's actually mapped to SELinux policy, MAXPERM=RWX is functionally like EXECMOD and actual RWX PTEs are functionally like EXECMEM. > > ... > > And making ACTIVATE_REGION a single-shot per page eliminates the need for > the MAXPERMS concept (see below). > > > If we keep only one MAXPERM, wouldn't this be the current behavior of > > mmap()/mprotect()? > > > > To be a bit more clear, system admin sets MAXPERM upper bound in the form of > > FILE__{READ|WRITE|EXECUTE|EXECMOD} of /dev/sgx/enclave. Then for a > > process/enclave, if what it requires falls below what's allowed on > > /dev/sgx/enclave, then everything will just work. Otherwise, it fails in the > > form of -EPERM returned from mmap()/mprotect(). Please note that MAXPERM here > > applies to "runtime" permissions, while "initial" permissions are taken care > > of by security_enclave_{load|init}. "initial" permissions could be more > > permissive than "runtime" permissions, e.g., RX is still required for initial > > code pages even though system admins could disable dynamically loaded code > > pages by *not* giving FILE__{EXECUTE|EXECMOD}. Therefore, the "initial" > > mapping would still have to be done by the driver (to bypass LSM), either via > > a new ioctl or as part of IOC_EINIT. > > Aha! > > Starting with Cedric's assertion that initial permissions can be taken > directly from SECINFO: > > - Initial permissions for *EADD* pages are explicitly handled via > sgx_enclave_load() with the exact SECINFO permissions. > > - Initial permissions for *EAUG* are unconditionally RW. EACCEPTCOPY > requires the target EPC page to be RW, and EACCEPT with RO is useless. > > - Runtime permissions break down as follows: > R - N/A, subset of RW (EAUG) > W - N/A, subset of RW (EAUG) and x86 paging can't do W > X - N/A, subset of RX (x86 paging can't do XO) Sure it can! You just have a hypervisor that maps a PA bit to EPT no-read. Then you can use that PA bit to suppress read. Also, Linux already abuses PKRU to simulate XO, although that won't work for enclaves. > RW - Handled by EAUG LSM hook (uses RW unconditionally) > WX - N/A, subset of RWX (x86 paging can't do WX) > RX - Handled by ACTIVATE_REGION > RWX - Handled by ACTIVATE_REGION > > In other words, if we define the SGX -> LSM calls as follows (minus the > file pointer and other params for brevity): > > - <prefix>_ACTIVATE_REGION(vma, perms) -> sgx_enclave_load(NULL, perms) > > - <prefix>_ADD_REGION(vma) -> sgx_enclave_load(vma, SECINFO.perms) > > - <prefix>_ADD_REGION(NULL) -> sgx_enclave_load(NULL, RW) > > then SGX and LSMs have all the information and hooks needed. The catch > is that the LSM semantics of sgx_enclave_load(..., RW) would need to be > different than normal shared memory, e.g. FILE__WRITE should *not* be > required, but that's ok since it's an SGX specific hook. And if for some > reason an LSM wanted to gate access to EAUG *without* FILE__EXECMOD, it'd > have the necessary information to do so. > > The userspace changes are fairly minimal: > > - For SGX1, use PROT_NONE for the initial mmap() and refactor ADD_PAGE > to ADD_REGION. > > - For SGX2, do an explicit ADD_REGION on the ranges to be EAUG'd, and an > ACTIVATE_REGION to make a region RX or R (no extra ioctl() required to > keep RW permissions). > > Because ACTIVATE_REGION can only be done once per page, to do *abitrary* > mprotect() transitions, userspace would need to set the added/activated > permissions to be a superset of the transitions, e.g. RW -> RX would > require RWX, but that's a non-issue. > I may be misunderstanding or just be biased to my own proposal, but this seems potentially more complicated and less flexible than the MAXPERM model. One of the main things that made me come up with MAXPERM is that I wanted to avoid any complicated PTE/VMA modification or runtime changes. So, with MAXPERM, we still need to track the MAXPERM bits per page, but we don't ever need to *change* them or to worry about what is or is not mapped anywhere at any given time. With ACTIVATE_REGION, don't we need to make sure that we don't have a second VMA pointing at the same pages? Or am I just confused? > - For SGX1 it's a nop since it's impossible to change the EPCM > permissions, i.e. the page would need to be RWX regardless. I may still be missing something, but, for SGX1, it's possible at least in principle for the enclave to request, via ocall or similar, that the untrusted runtime do mprotect(). It's not even such a bad idea. Honestly, enclaves *shouldn't* have anything actually writable and executable at once because the enclaves don't want to be easily exploited. > > - For SGX2, userspace can suck it up and request RWX to do completely > arbitrary transitions (working as intended), or the kernel can support > trimming (removing) pages from an enclave, which would allow userspace > to do "arbitrary" transitions by first removing the page.
On Tue, May 28, 2019 at 01:48:02PM -0700, Andy Lutomirski wrote: > On Tue, May 28, 2019 at 1:24 PM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > Actually, I think we do have everything we need from an LSM perspective. > > LSMs just need to understand that sgx_enclave_load() with a NULL vma > > implies a transition from RW. For example, SELinux would interpret > > sgx_enclave_load(NULL, RX) as requiring FILE__EXECMOD. > > You lost me here. What operation triggers this callback? And > wouldn't sgx_enclave_load(NULL, RX) sometimes be a transition from RO > or just some fresh executable zero bytes? An explicit ioctl() after EACCEPTCOPY to update the allowed permissions. For all intents and purposes, the EAUG'd page must start RW. Maybe a better way to phrase it is that at some point the page must be writable to have any value whatsover. EACCEPTCOPY explicitly requires the page to be at least RW. EACCEPT technically doesn't require RW, but a RO or RX zero page is useless. Userspace could still EACCEPT with RO or RX, but SGX would assume a minimum of RW for the purposes of the LSM check. > > As Cedric mentioned earlier, the host process doesn't necessarily know > > which pages will end up RW vs RX, i.e. sgx_enclave_load(NULL, RX) > > already has to be invoked at runtime, and when that happens, the kernel > > can take the opportunity to change the VMAs from MAY_RW to MAY_RX. > > > > For simplicity in the kernel and clarity in userspace, it makes sense to > > require an explicit ioctl() to add the to-be-EAUG'd range. That just > > leaves us wanting an ioctl() to set the post-EACCEPT{COPY} permissions. > > > > E.g.: > > > > ioctl(<prefix>_ADD_REGION, { NULL }) /* NULL == EAUG, MAY_RW */ > > > > mprotect(addr, size, RW); > > ... > > > > EACCEPTCOPY -> EAUG /* page fault handler */ > > > > ioctl(<prefix>_ACTIVATE_REGION, { addr, size, RX}) /* MAY_RX */ > > > > mprotect(addr, size, RX); > > In the maxperm model, this mprotect() will fail unless MAXPERM > contains RX, which could only happen if MAXPERM=RWX. So, regardless > of how it's actually mapped to SELinux policy, MAXPERM=RWX is > functionally like EXECMOD and actual RWX PTEs are functionally like > EXECMEM. Yep, same idea, except in the proposed flow ACTIVATE_REGION. > > ... > > > > And making ACTIVATE_REGION a single-shot per page eliminates the need for > > the MAXPERMS concept (see below). > > > > > If we keep only one MAXPERM, wouldn't this be the current behavior of > > > mmap()/mprotect()? > > > > > > To be a bit more clear, system admin sets MAXPERM upper bound in the form of > > > FILE__{READ|WRITE|EXECUTE|EXECMOD} of /dev/sgx/enclave. Then for a > > > process/enclave, if what it requires falls below what's allowed on > > > /dev/sgx/enclave, then everything will just work. Otherwise, it fails in the > > > form of -EPERM returned from mmap()/mprotect(). Please note that MAXPERM here > > > applies to "runtime" permissions, while "initial" permissions are taken care > > > of by security_enclave_{load|init}. "initial" permissions could be more > > > permissive than "runtime" permissions, e.g., RX is still required for initial > > > code pages even though system admins could disable dynamically loaded code > > > pages by *not* giving FILE__{EXECUTE|EXECMOD}. Therefore, the "initial" > > > mapping would still have to be done by the driver (to bypass LSM), either via > > > a new ioctl or as part of IOC_EINIT. > > > > Aha! > > > > Starting with Cedric's assertion that initial permissions can be taken > > directly from SECINFO: > > > > - Initial permissions for *EADD* pages are explicitly handled via > > sgx_enclave_load() with the exact SECINFO permissions. > > > > - Initial permissions for *EAUG* are unconditionally RW. EACCEPTCOPY > > requires the target EPC page to be RW, and EACCEPT with RO is useless. > > > > - Runtime permissions break down as follows: > > R - N/A, subset of RW (EAUG) > > W - N/A, subset of RW (EAUG) and x86 paging can't do W > > X - N/A, subset of RX (x86 paging can't do XO) > > Sure it can! You just have a hypervisor that maps a PA bit to EPT > no-read. Then you can use that PA bit to suppress read. Also, Linux > already abuses PKRU to simulate XO, although that won't work for > enclaves. Heh, I intentionally said "x86 paging" to rule out EPT :-) I'm pretty sure it's a moot point though, I have a hard time believing an LSM will allow RW->X and not RW->RX. > > RW - Handled by EAUG LSM hook (uses RW unconditionally) > > WX - N/A, subset of RWX (x86 paging can't do WX) > > RX - Handled by ACTIVATE_REGION > > RWX - Handled by ACTIVATE_REGION > > > > In other words, if we define the SGX -> LSM calls as follows (minus the > > file pointer and other params for brevity): > > > > - <prefix>_ACTIVATE_REGION(vma, perms) -> sgx_enclave_load(NULL, perms) > > > > - <prefix>_ADD_REGION(vma) -> sgx_enclave_load(vma, SECINFO.perms) > > > > - <prefix>_ADD_REGION(NULL) -> sgx_enclave_load(NULL, RW) > > > > then SGX and LSMs have all the information and hooks needed. The catch > > is that the LSM semantics of sgx_enclave_load(..., RW) would need to be > > different than normal shared memory, e.g. FILE__WRITE should *not* be > > required, but that's ok since it's an SGX specific hook. And if for some > > reason an LSM wanted to gate access to EAUG *without* FILE__EXECMOD, it'd > > have the necessary information to do so. > > > > The userspace changes are fairly minimal: > > > > - For SGX1, use PROT_NONE for the initial mmap() and refactor ADD_PAGE > > to ADD_REGION. > > > > - For SGX2, do an explicit ADD_REGION on the ranges to be EAUG'd, and an > > ACTIVATE_REGION to make a region RX or R (no extra ioctl() required to > > keep RW permissions). > > > > Because ACTIVATE_REGION can only be done once per page, to do *abitrary* > > mprotect() transitions, userspace would need to set the added/activated > > permissions to be a superset of the transitions, e.g. RW -> RX would > > require RWX, but that's a non-issue. > > > > I may be misunderstanding or just be biased to my own proposal, but > this seems potentially more complicated and less flexible than the > MAXPERM model. One of the main things that made me come up with > MAXPERM is that I wanted to avoid any complicated PTE/VMA modification > or runtime changes. So, with MAXPERM, we still need to track the > MAXPERM bits per page, but we don't ever need to *change* them or to > worry about what is or is not mapped anywhere at any given time. With > ACTIVATE_REGION, don't we need to make sure that we don't have a > second VMA pointing at the same pages? Or am I just confused? In theory, it's still your MAXPERM model, but with the unnecessary states removed and the others enforced/handled by the natural SGX transitions instead of explictly in ioctls. Underneath the hood the SGX driver would still need to track the MAXPERM. With SGX1, SECINFO == MAXPERM. With SGX2, ACTIVATE_REGION == MAXPERM, with the implication that the previous state is always RW. > > - For SGX1 it's a nop since it's impossible to change the EPCM > > permissions, i.e. the page would need to be RWX regardless. > > I may still be missing something, but, for SGX1, it's possible at > least in principle for the enclave to request, via ocall or similar, > that the untrusted runtime do mprotect(). It's not even such a bad > idea. Honestly, enclaves *shouldn't* have anything actually writable > and executable at once because the enclaves don't want to be easily > exploited. Yes, but the *EPCM* permissions are immutable. So if an enclave wants to do RW->RX it has to intialize its pages to RWX. And because the untrusted runtime is, ahem, untrusted, the enclave cannot rely on userspace to never map its pages RWX. In other words, from a enclave security perspective, an SGX1 enclave+runtime that uses RW->RX is no different than an enclave that uses RWX. Using your earlier terminology, an SGX1 enclave *should* get a dirty looks if maps a page RWX in the EPCM, even if it only intends RW->RX behavior. > > - For SGX2, userspace can suck it up and request RWX to do completely > > arbitrary transitions (working as intended), or the kernel can support > > trimming (removing) pages from an enclave, which would allow userspace > > to do "arbitrary" transitions by first removing the page.
On 5/28/19 4:24 PM, Sean Christopherson wrote: > On Sat, May 25, 2019 at 11:09:38PM -0700, Xing, Cedric wrote: >>> From: Andy Lutomirski [mailto:luto@kernel.org] >>> Sent: Saturday, May 25, 2019 5:58 PM >>> >>> On Sat, May 25, 2019 at 3:40 PM Xing, Cedric <cedric.xing@intel.com> wrote: >>>> >>>> If we think of EADD as a way of mmap()'ing an enclave file into memory, >>>> would this >>> security_enclave_load() be the same as >>> security_mmap_file(source_vma->vm_file, maxperm, MAP_PRIVATE), except that >>> the target is now EPC instead of regular pages? >>> >>> Hmm, that's clever. Although it seems plausible that an LSM would want to >>> allow RX or RWX of a given file page but only in the context of an approved >>> enclave, so I think it should still be its own hook. >> >> What do you mean by "in the context of an approved enclave"? EPC pages are >> *inaccessible* to any software until after EINIT. So it would never be a >> security concern to EADD a page with wrong permissions as long as the enclave >> would be denied eventually by LSM at EINIT. >> >> But I acknowledge the difference between loading a page into regular memory >> vs. into EPC. So it's beneficial to have a separate hook, which if not >> hooked, would pass through to security_mmap_file() by default? > > Mapping the enclave will still go through security_mmap_file(), the extra > security_enclave_load() hook allows the mmap() to use PROT_NONE. > >>> If it's going to be in an arbitrary file, then I think the signature needs to be more like: >>> >>> int security_enclave_init(struct vm_area_struct *sigstruct_vma, loff_t sigstruct_offset, >>> const sgx_sigstruct *sigstruct); >>> >>> So that the LSM still has the opportunity to base its decision on the contents of the >>> SIGSTRUCT. Actually, we need that change regardless. >> >> Wouldn't the pair of { sigstruct_vma, sigstruct_offset } be the same as just >> a pointer, because the VMA could be looked up using the pointer and the >> offset would then be (pointer - vma->vm_start)? > > VMA has vm_file, e.g. the .sigstruct file labeled by LSMs. That being > said, why does the LSM need the VMA? E.g. why not this? > > int security_enclave_init(struct file *file, struct sgx_sigstruct *sigstruct); > >>>> Loosely speaking, an enclave (including initial contents of all of its pages and their >>> permissions) and its MRENCLAVE are a 1-to-1 correspondence (given the collision resistant >>> property of SHA-2). So only one is needed for a decision, and either one would lead to the >>> same decision. So I don't see anything making any sense here. >>>> >>>> Theoretically speaking, if LSM can make a decision at EINIT by means of >>> security_enclave_load(), then security_enclave_load() is never needed. >>>> >>>> In practice, I support keeping both because security_enclave_load() can only approve an >>> enumerable set while security_enclave_load() can approve a non-enumerable set of enclaves. >>> Moreover, in order to determine the validity of a MRENCLAVE (as in development of a policy >>> or in creation of a white/black list), system admins will need the audit log produced by >>> security_enclave_load(). >>> >>> I'm confused. Things like MRSIGNER aren't known until the SIGSTRUCT shows >>> up. Also, security_enclave_load() provides no protection against loading a >>> mishmash of two different enclave files. I see security_enclave_init() as >>> "verify this SIGSTRUCT against your policy on who may sign enclaves and/or >>> grant EXECMOD depending on SIGSTRUCT" and security_enclave_load() as >>> "implement your EXECMOD / EXECUTE / WRITE / whatever policy and possibly >>> check enclave files for some label." >> >> Sorry for the confusion. I was saying the same thing except that the decision >> of security_enclave_load() doesn't have to depend on SIGSTRUCT. Given your >> prototype of security_enclave_load(), I think we are on the same page. I made >> the above comment to object to the idea of "require that the sigstruct be >> supplied before any EADD operations so that the maxperm decisions can depend >> on the sigstruct". > > Except that having the sigstruct allows using the sigstruct as the proxy > for the enclave. I think the last big disconnect is that Andy and I want > to tie everything to an enclave-specific file, i.e. sigstruct, while you > are proposing labeling /dev/sgx/enclave. If someone wants to cram several > sigstructs into a single file, so be it, but using /dev/sgx/enclave means > users can't do per-enclave permissions, period. > > What is your objection to working on the sigstruct? > >>>>>> Passing both would allow tying EXECMOD to /dev/sgx/enclave as >>>>>> Cedric wanted (without having to play games and pass >>>>>> /dev/sgx/enclave to security_enclave_load()), but I don't think >>>>>> there's anything fundamentally broken with using .sigstruct for >>>>>> EXECMOD. It requires more verbose labeling, but that's not a bad thing. >>>>> >>>>> The benefit of putting it on .sigstruct is that it can be per-enclave. >>>>> >>>>> As I understand it from Fedora packaging, the way this works on >>>>> distros is generally that a package will include some files and >>>>> their associated labels, and, if the package needs EXECMOD, then the >>>>> files are labeled with EXECMOD and the author of the relevant code might get a dirty >>> look. >>>>> >>>>> This could translate to the author of an exclave that needs RWX >>>>> regions getting a dirty look without leaking this permission into other enclaves. >>>>> >>>>> (In my opinion, the dirty looks are actually the best security >>>>> benefit of the entire concept of LSMs making RWX difficult. A >>>>> sufficiently creative attacker can almost always bypass W^X >>>>> restrictions once they’ve pwned you, but W^X makes it harder to pwn >>>>> you in the first place, and SELinux makes it really obvious when >>>>> packaging a program that doesn’t respect W^X. The upshot is that a >>>>> lot of programs got fixed.) >>>> >>>> I'm lost here. Dynamically linked enclaves, if running on SGX2, would need RW->RX, i.e. >>> FILE__EXECMOD on /dev/sgx/enclave. But they never need RWX, i.e. PROCESS__EXECMEM. >>> >>> Hmm. If we want to make this distinction, we need something a big richer >>> than my proposed callbacks. A check of the actual mprotect() / mmap() >>> permissions would also be needed. Specifically, allowing MAXPERM=RWX >>> wouldn't imply that PROT_WRITE | PROT_EXEC is allowed. > > Actually, I think we do have everything we need from an LSM perspective. > LSMs just need to understand that sgx_enclave_load() with a NULL vma > implies a transition from RW. For example, SELinux would interpret > sgx_enclave_load(NULL, RX) as requiring FILE__EXECMOD. > > As Cedric mentioned earlier, the host process doesn't necessarily know > which pages will end up RW vs RX, i.e. sgx_enclave_load(NULL, RX) > already has to be invoked at runtime, and when that happens, the kernel > can take the opportunity to change the VMAs from MAY_RW to MAY_RX. > > For simplicity in the kernel and clarity in userspace, it makes sense to > require an explicit ioctl() to add the to-be-EAUG'd range. That just > leaves us wanting an ioctl() to set the post-EACCEPT{COPY} permissions. > > E.g.: > > ioctl(<prefix>_ADD_REGION, { NULL }) /* NULL == EAUG, MAY_RW */ > > mprotect(addr, size, RW); > ... > > EACCEPTCOPY -> EAUG /* page fault handler */ > > ioctl(<prefix>_ACTIVATE_REGION, { addr, size, RX}) /* MAY_RX */ > > mprotect(addr, size, RX); > > ... > > And making ACTIVATE_REGION a single-shot per page eliminates the need for > the MAXPERMS concept (see below). > >> If we keep only one MAXPERM, wouldn't this be the current behavior of >> mmap()/mprotect()? >> >> To be a bit more clear, system admin sets MAXPERM upper bound in the form of >> FILE__{READ|WRITE|EXECUTE|EXECMOD} of /dev/sgx/enclave. Then for a >> process/enclave, if what it requires falls below what's allowed on >> /dev/sgx/enclave, then everything will just work. Otherwise, it fails in the >> form of -EPERM returned from mmap()/mprotect(). Please note that MAXPERM here >> applies to "runtime" permissions, while "initial" permissions are taken care >> of by security_enclave_{load|init}. "initial" permissions could be more >> permissive than "runtime" permissions, e.g., RX is still required for initial >> code pages even though system admins could disable dynamically loaded code >> pages by *not* giving FILE__{EXECUTE|EXECMOD}. Therefore, the "initial" >> mapping would still have to be done by the driver (to bypass LSM), either via >> a new ioctl or as part of IOC_EINIT. > > Aha! > > Starting with Cedric's assertion that initial permissions can be taken > directly from SECINFO: > > - Initial permissions for *EADD* pages are explicitly handled via > sgx_enclave_load() with the exact SECINFO permissions. > > - Initial permissions for *EAUG* are unconditionally RW. EACCEPTCOPY > requires the target EPC page to be RW, and EACCEPT with RO is useless. > > - Runtime permissions break down as follows: > R - N/A, subset of RW (EAUG) > W - N/A, subset of RW (EAUG) and x86 paging can't do W > X - N/A, subset of RX (x86 paging can't do XO) > RW - Handled by EAUG LSM hook (uses RW unconditionally) > WX - N/A, subset of RWX (x86 paging can't do WX) > RX - Handled by ACTIVATE_REGION > RWX - Handled by ACTIVATE_REGION > > In other words, if we define the SGX -> LSM calls as follows (minus the > file pointer and other params for brevity): > > - <prefix>_ACTIVATE_REGION(vma, perms) -> sgx_enclave_load(NULL, perms) > > - <prefix>_ADD_REGION(vma) -> sgx_enclave_load(vma, SECINFO.perms) > > - <prefix>_ADD_REGION(NULL) -> sgx_enclave_load(NULL, RW) > > then SGX and LSMs have all the information and hooks needed. The catch > is that the LSM semantics of sgx_enclave_load(..., RW) would need to be > different than normal shared memory, e.g. FILE__WRITE should *not* be > required, but that's ok since it's an SGX specific hook. And if for some > reason an LSM wanted to gate access to EAUG *without* FILE__EXECMOD, it'd > have the necessary information to do so. Assuming that sgx_enclave_load() is a LSM hook (probably named security_enclave_load() instead), then: a) Does the sigstruct file get passed to this hook in every case, even when vma is NULL? I think the answer is yes, just want to confirm. b) Should we use a different hook for ACTIVATE_REGION than for ADD_REGION or is the distinction between them irrelevant/unnecessary from an access control point of view? At present LSM/SELinux won't be able to distinguish ACTIVATE_REGION(vma, RW) from ADD_REGION(NULL) above since they will both invoke the same hook with the same arguments IIUC. Does it matter? It's ok if the answer is no, just want to confirm. c) Is there still also a separate security_enclave_init() hook that will be called, and if so, how does it differ and when is it called relative to security_enclave_load()? d) What checks were you envisioning each of these calls making? With the separate security_enclave_*() hooks, we could define and use new ENCLAVE__* permissions, e.g. ENCLAVE__LOAD, ENCLAVE__INIT, ENCLAVE__EXECUTE, ENCLAVE__EXECMEM, ENCLAVE__EXECMOD, if we want to distinguish these operations from regular file mmap/mprotect operations. > > The userspace changes are fairly minimal: > > - For SGX1, use PROT_NONE for the initial mmap() and refactor ADD_PAGE > to ADD_REGION. > > - For SGX2, do an explicit ADD_REGION on the ranges to be EAUG'd, and an > ACTIVATE_REGION to make a region RX or R (no extra ioctl() required to > keep RW permissions). > > Because ACTIVATE_REGION can only be done once per page, to do *abitrary* > mprotect() transitions, userspace would need to set the added/activated > permissions to be a superset of the transitions, e.g. RW -> RX would > require RWX, but that's a non-issue. > > - For SGX1 it's a nop since it's impossible to change the EPCM > permissions, i.e. the page would need to be RWX regardless. > > - For SGX2, userspace can suck it up and request RWX to do completely > arbitrary transitions (working as intended), or the kernel can support > trimming (removing) pages from an enclave, which would allow userspace > to do "arbitrary" transitions by first removing the page. >
> From: Christopherson, Sean J > Sent: Tuesday, May 28, 2019 2:41 PM > > On Tue, May 28, 2019 at 01:48:02PM -0700, Andy Lutomirski wrote: > > On Tue, May 28, 2019 at 1:24 PM Sean Christopherson > > <sean.j.christopherson@intel.com> wrote: > > > > > > Actually, I think we do have everything we need from an LSM perspective. > > > LSMs just need to understand that sgx_enclave_load() with a NULL vma > > > implies a transition from RW. For example, SELinux would interpret > > > sgx_enclave_load(NULL, RX) as requiring FILE__EXECMOD. > > > > You lost me here. What operation triggers this callback? And > > wouldn't sgx_enclave_load(NULL, RX) sometimes be a transition from RO > > or just some fresh executable zero bytes? > > An explicit ioctl() after EACCEPTCOPY to update the allowed permissions. > For all intents and purposes, the EAUG'd page must start RW. Maybe a better way to phrase > it is that at some point the page must be writable to have any value whatsover. > EACCEPTCOPY explicitly requires the page to be at least RW. EACCEPT technically doesn't > require RW, but a RO or RX zero page is useless. Userspace could still EACCEPT with RO or > RX, but SGX would assume a minimum of RW for the purposes of the LSM check. Why is an explicit ioctl() necessary after EACCEPTCOPY? Or why is mprotect() not sufficient? I tend to agree on Andy's MAXPERM model where MAXPERM never changes once established. > > > > As Cedric mentioned earlier, the host process doesn't necessarily > > > know which pages will end up RW vs RX, i.e. sgx_enclave_load(NULL, > > > RX) already has to be invoked at runtime, and when that happens, the > > > kernel can take the opportunity to change the VMAs from MAY_RW to MAY_RX. > > > > > > For simplicity in the kernel and clarity in userspace, it makes > > > sense to require an explicit ioctl() to add the to-be-EAUG'd range. > > > That just leaves us wanting an ioctl() to set the post-EACCEPT{COPY} permissions. > > > > > > E.g.: > > > > > > ioctl(<prefix>_ADD_REGION, { NULL }) /* NULL == EAUG, MAY_RW */ > > > > > > mprotect(addr, size, RW); > > > ... > > > > > > EACCEPTCOPY -> EAUG /* page fault handler */ > > > > > > ioctl(<prefix>_ACTIVATE_REGION, { addr, size, RX}) /* MAY_RX */ > > > > > > mprotect(addr, size, RX); > > > > In the maxperm model, this mprotect() will fail unless MAXPERM > > contains RX, which could only happen if MAXPERM=RWX. So, regardless > > of how it's actually mapped to SELinux policy, MAXPERM=RWX is > > functionally like EXECMOD and actual RWX PTEs are functionally like > > EXECMEM. > > Yep, same idea, except in the proposed flow ACTIVATE_REGION. > > > > ... > > > > > > And making ACTIVATE_REGION a single-shot per page eliminates the > > > need for the MAXPERMS concept (see below). > > > > > > > If we keep only one MAXPERM, wouldn't this be the current behavior > > > > of mmap()/mprotect()? > > > > > > > > To be a bit more clear, system admin sets MAXPERM upper bound in > > > > the form of FILE__{READ|WRITE|EXECUTE|EXECMOD} of > > > > /dev/sgx/enclave. Then for a process/enclave, if what it requires > > > > falls below what's allowed on /dev/sgx/enclave, then everything > > > > will just work. Otherwise, it fails in the form of -EPERM returned > > > > from mmap()/mprotect(). Please note that MAXPERM here applies to > > > > "runtime" permissions, while "initial" permissions are taken care > > > > of by security_enclave_{load|init}. "initial" permissions could be > > > > more permissive than "runtime" permissions, e.g., RX is still > > > > required for initial code pages even though system admins could disable dynamically > loaded code pages by *not* giving FILE__{EXECUTE|EXECMOD}. Therefore, the "initial" > > > > mapping would still have to be done by the driver (to bypass LSM), > > > > either via a new ioctl or as part of IOC_EINIT. > > > > > > Aha! > > > > > > Starting with Cedric's assertion that initial permissions can be > > > taken directly from SECINFO: > > > > > > - Initial permissions for *EADD* pages are explicitly handled via > > > sgx_enclave_load() with the exact SECINFO permissions. > > > > > > - Initial permissions for *EAUG* are unconditionally RW. EACCEPTCOPY > > > requires the target EPC page to be RW, and EACCEPT with RO is useless. > > > > > > - Runtime permissions break down as follows: > > > R - N/A, subset of RW (EAUG) > > > W - N/A, subset of RW (EAUG) and x86 paging can't do W > > > X - N/A, subset of RX (x86 paging can't do XO) > > > > Sure it can! You just have a hypervisor that maps a PA bit to EPT > > no-read. Then you can use that PA bit to suppress read. Also, Linux > > already abuses PKRU to simulate XO, although that won't work for > > enclaves. > > Heh, I intentionally said "x86 paging" to rule out EPT :-) I'm pretty sure it's a moot > point though, I have a hard time believing an LSM will allow RW->X and not RW->RX. > > > > RW - Handled by EAUG LSM hook (uses RW unconditionally) > > > WX - N/A, subset of RWX (x86 paging can't do WX) > > > RX - Handled by ACTIVATE_REGION > > > RWX - Handled by ACTIVATE_REGION > > > > > > In other words, if we define the SGX -> LSM calls as follows (minus > > > the file pointer and other params for brevity): > > > > > > - <prefix>_ACTIVATE_REGION(vma, perms) -> sgx_enclave_load(NULL, > > > perms) I'm not sure on what security_enclave_load()'s decision would be based. > > > > > > - <prefix>_ADD_REGION(vma) -> sgx_enclave_load(vma, SECINFO.perms) > > > > > > - <prefix>_ADD_REGION(NULL) -> sgx_enclave_load(NULL, RW) > > > > > > then SGX and LSMs have all the information and hooks needed. The > > > catch is that the LSM semantics of sgx_enclave_load(..., RW) would > > > need to be different than normal shared memory, e.g. FILE__WRITE > > > should *not* be required, but that's ok since it's an SGX specific > > > hook. And if for some reason an LSM wanted to gate access to EAUG > > > *without* FILE__EXECMOD, it'd have the necessary information to do so. > > > > > > The userspace changes are fairly minimal: > > > > > > - For SGX1, use PROT_NONE for the initial mmap() and refactor ADD_PAGE > > > to ADD_REGION. > > > > > > - For SGX2, do an explicit ADD_REGION on the ranges to be EAUG'd, and an > > > ACTIVATE_REGION to make a region RX or R (no extra ioctl() required to > > > keep RW permissions). > > > > > > Because ACTIVATE_REGION can only be done once per page, to do > > > *abitrary* > > > mprotect() transitions, userspace would need to set the > > > added/activated permissions to be a superset of the transitions, > > > e.g. RW -> RX would require RWX, but that's a non-issue. > > > > > > > I may be misunderstanding or just be biased to my own proposal, but > > this seems potentially more complicated and less flexible than the > > MAXPERM model. One of the main things that made me come up with > > MAXPERM is that I wanted to avoid any complicated PTE/VMA modification > > or runtime changes. So, with MAXPERM, we still need to track the > > MAXPERM bits per page, but we don't ever need to *change* them or to > > worry about what is or is not mapped anywhere at any given time. With > > ACTIVATE_REGION, don't we need to make sure that we don't have a > > second VMA pointing at the same pages? Or am I just confused? > > In theory, it's still your MAXPERM model, but with the unnecessary states removed and the > others enforced/handled by the natural SGX transitions instead of explictly in ioctls. > Underneath the hood the SGX driver would still need to track the MAXPERM. What are the "unnecessary states" removed? I'm not sure understand the proposal fully. The whole thing looks to me like the driver is undertaking things that should/would otherwise be done by mmap()/mprotect() syscalls. It also imposes unnecessary restrictions on user mode code, such as mmap(PROT_NONE), ACTIVATE_REGION can be called only once, etc. What'd happen if ACTIVATE_REGION is called with a range spanning multiple/partial VMAs? What'd happen if an enclave was unmapped than mapped again? I'd say the proposal is unintuitive at least. In theory, if the driver can keep track of MAXPERM for all pages within an enclave, then it could fail mmap() if the requested prot conflicts with any page's MAXPERM within that range. Otherwise, MAXPERM could be copied into VM_MAY* flags then mprotect() will just follow through. Wouldn't that be a much simpler and more intuitive approach? > > With SGX1, SECINFO == MAXPERM. With SGX2, ACTIVATE_REGION == MAXPERM, with the > implication that the previous state is always RW. > > > > - For SGX1 it's a nop since it's impossible to change the EPCM > > > permissions, i.e. the page would need to be RWX regardless. > > > > I may still be missing something, but, for SGX1, it's possible at > > least in principle for the enclave to request, via ocall or similar, > > that the untrusted runtime do mprotect(). It's not even such a bad > > idea. Honestly, enclaves *shouldn't* have anything actually writable > > and executable at once because the enclaves don't want to be easily > > exploited. > > Yes, but the *EPCM* permissions are immutable. So if an enclave wants to do RW->RX it has > to intialize its pages to RWX. And because the untrusted runtime is, ahem, untrusted, the > enclave cannot rely on userspace to never map its pages RWX. In other words, from a > enclave security perspective, an SGX1 enclave+runtime that uses RW->RX is no different > than an enclave that uses RWX. Using your earlier terminology, an SGX1 enclave *should* > get a dirty looks if maps a page RWX in the EPCM, even if it only intends RW->RX behavior. > > > > - For SGX2, userspace can suck it up and request RWX to do completely > > > arbitrary transitions (working as intended), or the kernel can support > > > trimming (removing) pages from an enclave, which would allow userspace > > > to do "arbitrary" transitions by first removing the page.
> From: linux-sgx-owner@vger.kernel.org [mailto:linux-sgx-owner@vger.kernel.org] On Behalf > Of Stephen Smalley > Sent: Wednesday, May 29, 2019 7:08 AM > > On 5/28/19 4:24 PM, Sean Christopherson wrote: > > On Sat, May 25, 2019 at 11:09:38PM -0700, Xing, Cedric wrote: > >>> From: Andy Lutomirski [mailto:luto@kernel.org] > >>> Sent: Saturday, May 25, 2019 5:58 PM > >>> > >>> On Sat, May 25, 2019 at 3:40 PM Xing, Cedric <cedric.xing@intel.com> wrote: > >>>> > >>>> If we think of EADD as a way of mmap()'ing an enclave file into memory, > >>>> would this > >>> security_enclave_load() be the same as > >>> security_mmap_file(source_vma->vm_file, maxperm, MAP_PRIVATE), except that > >>> the target is now EPC instead of regular pages? > >>> > >>> Hmm, that's clever. Although it seems plausible that an LSM would want to > >>> allow RX or RWX of a given file page but only in the context of an approved > >>> enclave, so I think it should still be its own hook. > >> > >> What do you mean by "in the context of an approved enclave"? EPC pages are > >> *inaccessible* to any software until after EINIT. So it would never be a > >> security concern to EADD a page with wrong permissions as long as the enclave > >> would be denied eventually by LSM at EINIT. > >> > >> But I acknowledge the difference between loading a page into regular memory > >> vs. into EPC. So it's beneficial to have a separate hook, which if not > >> hooked, would pass through to security_mmap_file() by default? > > > > Mapping the enclave will still go through security_mmap_file(), the extra > > security_enclave_load() hook allows the mmap() to use PROT_NONE. > > > >>> If it's going to be in an arbitrary file, then I think the signature needs to be more > like: > >>> > >>> int security_enclave_init(struct vm_area_struct *sigstruct_vma, loff_t > sigstruct_offset, > >>> const sgx_sigstruct *sigstruct); > >>> > >>> So that the LSM still has the opportunity to base its decision on the contents of the > >>> SIGSTRUCT. Actually, we need that change regardless. > >> > >> Wouldn't the pair of { sigstruct_vma, sigstruct_offset } be the same as just > >> a pointer, because the VMA could be looked up using the pointer and the > >> offset would then be (pointer - vma->vm_start)? > > > > VMA has vm_file, e.g. the .sigstruct file labeled by LSMs. That being > > said, why does the LSM need the VMA? E.g. why not this? > > > > int security_enclave_init(struct file *file, struct sgx_sigstruct *sigstruct); > > > >>>> Loosely speaking, an enclave (including initial contents of all of its pages and > their > >>> permissions) and its MRENCLAVE are a 1-to-1 correspondence (given the collision > resistant > >>> property of SHA-2). So only one is needed for a decision, and either one would lead to > the > >>> same decision. So I don't see anything making any sense here. > >>>> > >>>> Theoretically speaking, if LSM can make a decision at EINIT by means of > >>> security_enclave_load(), then security_enclave_load() is never needed. > >>>> > >>>> In practice, I support keeping both because security_enclave_load() can only approve > an > >>> enumerable set while security_enclave_load() can approve a non-enumerable set of > enclaves. > >>> Moreover, in order to determine the validity of a MRENCLAVE (as in development of a > policy > >>> or in creation of a white/black list), system admins will need the audit log produced > by > >>> security_enclave_load(). > >>> > >>> I'm confused. Things like MRSIGNER aren't known until the SIGSTRUCT shows > >>> up. Also, security_enclave_load() provides no protection against loading a > >>> mishmash of two different enclave files. I see security_enclave_init() as > >>> "verify this SIGSTRUCT against your policy on who may sign enclaves and/or > >>> grant EXECMOD depending on SIGSTRUCT" and security_enclave_load() as > >>> "implement your EXECMOD / EXECUTE / WRITE / whatever policy and possibly > >>> check enclave files for some label." > >> > >> Sorry for the confusion. I was saying the same thing except that the decision > >> of security_enclave_load() doesn't have to depend on SIGSTRUCT. Given your > >> prototype of security_enclave_load(), I think we are on the same page. I made > >> the above comment to object to the idea of "require that the sigstruct be > >> supplied before any EADD operations so that the maxperm decisions can depend > >> on the sigstruct". > > > > Except that having the sigstruct allows using the sigstruct as the proxy > > for the enclave. I think the last big disconnect is that Andy and I want > > to tie everything to an enclave-specific file, i.e. sigstruct, while you > > are proposing labeling /dev/sgx/enclave. If someone wants to cram several > > sigstructs into a single file, so be it, but using /dev/sgx/enclave means > > users can't do per-enclave permissions, period. > > > > What is your objection to working on the sigstruct? > > > >>>>>> Passing both would allow tying EXECMOD to /dev/sgx/enclave as > >>>>>> Cedric wanted (without having to play games and pass > >>>>>> /dev/sgx/enclave to security_enclave_load()), but I don't think > >>>>>> there's anything fundamentally broken with using .sigstruct for > >>>>>> EXECMOD. It requires more verbose labeling, but that's not a bad thing. > >>>>> > >>>>> The benefit of putting it on .sigstruct is that it can be per-enclave. > >>>>> > >>>>> As I understand it from Fedora packaging, the way this works on > >>>>> distros is generally that a package will include some files and > >>>>> their associated labels, and, if the package needs EXECMOD, then the > >>>>> files are labeled with EXECMOD and the author of the relevant code might get a dirty > >>> look. > >>>>> > >>>>> This could translate to the author of an exclave that needs RWX > >>>>> regions getting a dirty look without leaking this permission into other enclaves. > >>>>> > >>>>> (In my opinion, the dirty looks are actually the best security > >>>>> benefit of the entire concept of LSMs making RWX difficult. A > >>>>> sufficiently creative attacker can almost always bypass W^X > >>>>> restrictions once they’ve pwned you, but W^X makes it harder to pwn > >>>>> you in the first place, and SELinux makes it really obvious when > >>>>> packaging a program that doesn’t respect W^X. The upshot is that a > >>>>> lot of programs got fixed.) > >>>> > >>>> I'm lost here. Dynamically linked enclaves, if running on SGX2, would need RW->RX, > i.e. > >>> FILE__EXECMOD on /dev/sgx/enclave. But they never need RWX, i.e. PROCESS__EXECMEM. > >>> > >>> Hmm. If we want to make this distinction, we need something a big richer > >>> than my proposed callbacks. A check of the actual mprotect() / mmap() > >>> permissions would also be needed. Specifically, allowing MAXPERM=RWX > >>> wouldn't imply that PROT_WRITE | PROT_EXEC is allowed. > > > > Actually, I think we do have everything we need from an LSM perspective. > > LSMs just need to understand that sgx_enclave_load() with a NULL vma > > implies a transition from RW. For example, SELinux would interpret > > sgx_enclave_load(NULL, RX) as requiring FILE__EXECMOD. > > > > As Cedric mentioned earlier, the host process doesn't necessarily know > > which pages will end up RW vs RX, i.e. sgx_enclave_load(NULL, RX) > > already has to be invoked at runtime, and when that happens, the kernel > > can take the opportunity to change the VMAs from MAY_RW to MAY_RX. > > > > For simplicity in the kernel and clarity in userspace, it makes sense to > > require an explicit ioctl() to add the to-be-EAUG'd range. That just > > leaves us wanting an ioctl() to set the post-EACCEPT{COPY} permissions. > > > > E.g.: > > > > ioctl(<prefix>_ADD_REGION, { NULL }) /* NULL == EAUG, MAY_RW */ > > > > mprotect(addr, size, RW); > > ... > > > > EACCEPTCOPY -> EAUG /* page fault handler */ > > > > ioctl(<prefix>_ACTIVATE_REGION, { addr, size, RX}) /* MAY_RX */ > > > > mprotect(addr, size, RX); > > > > ... > > > > And making ACTIVATE_REGION a single-shot per page eliminates the need for > > the MAXPERMS concept (see below). > > > >> If we keep only one MAXPERM, wouldn't this be the current behavior of > >> mmap()/mprotect()? > >> > >> To be a bit more clear, system admin sets MAXPERM upper bound in the form of > >> FILE__{READ|WRITE|EXECUTE|EXECMOD} of /dev/sgx/enclave. Then for a > >> process/enclave, if what it requires falls below what's allowed on > >> /dev/sgx/enclave, then everything will just work. Otherwise, it fails in the > >> form of -EPERM returned from mmap()/mprotect(). Please note that MAXPERM here > >> applies to "runtime" permissions, while "initial" permissions are taken care > >> of by security_enclave_{load|init}. "initial" permissions could be more > >> permissive than "runtime" permissions, e.g., RX is still required for initial > >> code pages even though system admins could disable dynamically loaded code > >> pages by *not* giving FILE__{EXECUTE|EXECMOD}. Therefore, the "initial" > >> mapping would still have to be done by the driver (to bypass LSM), either via > >> a new ioctl or as part of IOC_EINIT. > > > > Aha! > > > > Starting with Cedric's assertion that initial permissions can be taken > > directly from SECINFO: > > > > - Initial permissions for *EADD* pages are explicitly handled via > > sgx_enclave_load() with the exact SECINFO permissions. > > > > - Initial permissions for *EAUG* are unconditionally RW. EACCEPTCOPY > > requires the target EPC page to be RW, and EACCEPT with RO is useless. > > > > - Runtime permissions break down as follows: > > R - N/A, subset of RW (EAUG) > > W - N/A, subset of RW (EAUG) and x86 paging can't do W > > X - N/A, subset of RX (x86 paging can't do XO) > > RW - Handled by EAUG LSM hook (uses RW unconditionally) > > WX - N/A, subset of RWX (x86 paging can't do WX) > > RX - Handled by ACTIVATE_REGION > > RWX - Handled by ACTIVATE_REGION > > > > In other words, if we define the SGX -> LSM calls as follows (minus the > > file pointer and other params for brevity): > > > > - <prefix>_ACTIVATE_REGION(vma, perms) -> sgx_enclave_load(NULL, perms) > > > > - <prefix>_ADD_REGION(vma) -> sgx_enclave_load(vma, SECINFO.perms) > > > > - <prefix>_ADD_REGION(NULL) -> sgx_enclave_load(NULL, RW) > > > > then SGX and LSMs have all the information and hooks needed. The catch > > is that the LSM semantics of sgx_enclave_load(..., RW) would need to be > > different than normal shared memory, e.g. FILE__WRITE should *not* be > > required, but that's ok since it's an SGX specific hook. And if for some > > reason an LSM wanted to gate access to EAUG *without* FILE__EXECMOD, it'd > > have the necessary information to do so. > > Assuming that sgx_enclave_load() is a LSM hook (probably named > security_enclave_load() instead), then: > > a) Does the sigstruct file get passed to this hook in every case, even > when vma is NULL? I think the answer is yes, just want to confirm. I'm confused. In the case of EADD (non-NULL vma), are we passing both vma and sigstruct file? If so, which file dictates allowed permissions, vma->vm_file or sigstruct, or both??? In the case of EAUG (NULL vma), all other parameters are constant for any given enclave. Then why do we call this same hook for every region added, assuming the hook will return the same value everytime anyway? And it looks like ACTIVATE_REGION is needed only because the proposed security_enclave_load() would base its decision on the sigstruct file. An alternative is to base that decision on /dev/sgx/enclave. Of course the former has finer granularity but is that really necessary? From security perspective, only the weakest link matters. FILE__EXECMOD on a regular shared object could allow exploits of all bugs throughout the host process because code within that shared object is modifiable by not only itself but also any code within that same process. In contrast, FILE__EXECMOD on /dev/sgx/enclave only allows enclaves to modify themselves. They cannot modify each other, neither can "untrusted" code outside of enclaves modify any of them. So it doesn't look like a weaker link to me. Moreover, requiring FILE__EXECMOD on sigstruct means it could be used as a target buffer for code injection attacks. IMHO that *lowers* the security of the whole process. > > b) Should we use a different hook for ACTIVATE_REGION than for > ADD_REGION or is the distinction between them irrelevant/unnecessary > from an access control point of view? At present LSM/SELinux won't be > able to distinguish ACTIVATE_REGION(vma, RW) from ADD_REGION(NULL) above > since they will both invoke the same hook with the same arguments IIUC. > Does it matter? It's ok if the answer is no, just want to confirm. > > c) Is there still also a separate security_enclave_init() hook that will > be called, and if so, how does it differ and when is it called relative > to security_enclave_load()? I think security_enclave_init() will always be useful, as it offers a way for LSM to implement whitelisting/blacklisting. Of course an LSM module like SELinux can look into the backing inode too. I think the hook should have a signature like: int security_enclave_init(struct sgx_sigstruct __user *sigstruct); An LSM that cares about the backing file could look into vm_file of the VMA covering the buffer, while an LSM that cares the sigstruct itself (e.g. signing key) could just look into the buffer. > > d) What checks were you envisioning each of these calls making? > > With the separate security_enclave_*() hooks, we could define and use > new ENCLAVE__* permissions, e.g. ENCLAVE__LOAD, ENCLAVE__INIT, > ENCLAVE__EXECUTE, ENCLAVE__EXECMEM, ENCLAVE__EXECMOD, if we want to > distinguish these operations from regular file mmap/mprotect operations. I'm not sure if these ENCLAVE__* flags are an overkill, unless we want to enforce an enclave file cannot be loaded as a regular shared object or vice versa. > > > > > The userspace changes are fairly minimal: > > > > - For SGX1, use PROT_NONE for the initial mmap() and refactor ADD_PAGE > > to ADD_REGION. > > > > - For SGX2, do an explicit ADD_REGION on the ranges to be EAUG'd, and an > > ACTIVATE_REGION to make a region RX or R (no extra ioctl() required to > > keep RW permissions). > > > > Because ACTIVATE_REGION can only be done once per page, to do *abitrary* > > mprotect() transitions, userspace would need to set the added/activated > > permissions to be a superset of the transitions, e.g. RW -> RX would > > require RWX, but that's a non-issue. > > > > - For SGX1 it's a nop since it's impossible to change the EPCM > > permissions, i.e. the page would need to be RWX regardless. > > > > - For SGX2, userspace can suck it up and request RWX to do completely > > arbitrary transitions (working as intended), or the kernel can support > > trimming (removing) pages from an enclave, which would allow userspace > > to do "arbitrary" transitions by first removing the page. > >
On 5/30/19 2:12 AM, Xing, Cedric wrote: >> From: linux-sgx-owner@vger.kernel.org [mailto:linux-sgx-owner@vger.kernel.org] On Behalf >> Of Stephen Smalley >> Sent: Wednesday, May 29, 2019 7:08 AM >> >> On 5/28/19 4:24 PM, Sean Christopherson wrote: >>> On Sat, May 25, 2019 at 11:09:38PM -0700, Xing, Cedric wrote: >>>>> From: Andy Lutomirski [mailto:luto@kernel.org] >>>>> Sent: Saturday, May 25, 2019 5:58 PM >>>>> >>>>> On Sat, May 25, 2019 at 3:40 PM Xing, Cedric <cedric.xing@intel.com> wrote: >>>>>> >>>>>> If we think of EADD as a way of mmap()'ing an enclave file into memory, >>>>>> would this >>>>> security_enclave_load() be the same as >>>>> security_mmap_file(source_vma->vm_file, maxperm, MAP_PRIVATE), except that >>>>> the target is now EPC instead of regular pages? >>>>> >>>>> Hmm, that's clever. Although it seems plausible that an LSM would want to >>>>> allow RX or RWX of a given file page but only in the context of an approved >>>>> enclave, so I think it should still be its own hook. >>>> >>>> What do you mean by "in the context of an approved enclave"? EPC pages are >>>> *inaccessible* to any software until after EINIT. So it would never be a >>>> security concern to EADD a page with wrong permissions as long as the enclave >>>> would be denied eventually by LSM at EINIT. >>>> >>>> But I acknowledge the difference between loading a page into regular memory >>>> vs. into EPC. So it's beneficial to have a separate hook, which if not >>>> hooked, would pass through to security_mmap_file() by default? >>> >>> Mapping the enclave will still go through security_mmap_file(), the extra >>> security_enclave_load() hook allows the mmap() to use PROT_NONE. >>> >>>>> If it's going to be in an arbitrary file, then I think the signature needs to be more >> like: >>>>> >>>>> int security_enclave_init(struct vm_area_struct *sigstruct_vma, loff_t >> sigstruct_offset, >>>>> const sgx_sigstruct *sigstruct); >>>>> >>>>> So that the LSM still has the opportunity to base its decision on the contents of the >>>>> SIGSTRUCT. Actually, we need that change regardless. >>>> >>>> Wouldn't the pair of { sigstruct_vma, sigstruct_offset } be the same as just >>>> a pointer, because the VMA could be looked up using the pointer and the >>>> offset would then be (pointer - vma->vm_start)? >>> >>> VMA has vm_file, e.g. the .sigstruct file labeled by LSMs. That being >>> said, why does the LSM need the VMA? E.g. why not this? >>> >>> int security_enclave_init(struct file *file, struct sgx_sigstruct *sigstruct); >>> >>>>>> Loosely speaking, an enclave (including initial contents of all of its pages and >> their >>>>> permissions) and its MRENCLAVE are a 1-to-1 correspondence (given the collision >> resistant >>>>> property of SHA-2). So only one is needed for a decision, and either one would lead to >> the >>>>> same decision. So I don't see anything making any sense here. >>>>>> >>>>>> Theoretically speaking, if LSM can make a decision at EINIT by means of >>>>> security_enclave_load(), then security_enclave_load() is never needed. >>>>>> >>>>>> In practice, I support keeping both because security_enclave_load() can only approve >> an >>>>> enumerable set while security_enclave_load() can approve a non-enumerable set of >> enclaves. >>>>> Moreover, in order to determine the validity of a MRENCLAVE (as in development of a >> policy >>>>> or in creation of a white/black list), system admins will need the audit log produced >> by >>>>> security_enclave_load(). >>>>> >>>>> I'm confused. Things like MRSIGNER aren't known until the SIGSTRUCT shows >>>>> up. Also, security_enclave_load() provides no protection against loading a >>>>> mishmash of two different enclave files. I see security_enclave_init() as >>>>> "verify this SIGSTRUCT against your policy on who may sign enclaves and/or >>>>> grant EXECMOD depending on SIGSTRUCT" and security_enclave_load() as >>>>> "implement your EXECMOD / EXECUTE / WRITE / whatever policy and possibly >>>>> check enclave files for some label." >>>> >>>> Sorry for the confusion. I was saying the same thing except that the decision >>>> of security_enclave_load() doesn't have to depend on SIGSTRUCT. Given your >>>> prototype of security_enclave_load(), I think we are on the same page. I made >>>> the above comment to object to the idea of "require that the sigstruct be >>>> supplied before any EADD operations so that the maxperm decisions can depend >>>> on the sigstruct". >>> >>> Except that having the sigstruct allows using the sigstruct as the proxy >>> for the enclave. I think the last big disconnect is that Andy and I want >>> to tie everything to an enclave-specific file, i.e. sigstruct, while you >>> are proposing labeling /dev/sgx/enclave. If someone wants to cram several >>> sigstructs into a single file, so be it, but using /dev/sgx/enclave means >>> users can't do per-enclave permissions, period. >>> >>> What is your objection to working on the sigstruct? >>> >>>>>>>> Passing both would allow tying EXECMOD to /dev/sgx/enclave as >>>>>>>> Cedric wanted (without having to play games and pass >>>>>>>> /dev/sgx/enclave to security_enclave_load()), but I don't think >>>>>>>> there's anything fundamentally broken with using .sigstruct for >>>>>>>> EXECMOD. It requires more verbose labeling, but that's not a bad thing. >>>>>>> >>>>>>> The benefit of putting it on .sigstruct is that it can be per-enclave. >>>>>>> >>>>>>> As I understand it from Fedora packaging, the way this works on >>>>>>> distros is generally that a package will include some files and >>>>>>> their associated labels, and, if the package needs EXECMOD, then the >>>>>>> files are labeled with EXECMOD and the author of the relevant code might get a dirty >>>>> look. >>>>>>> >>>>>>> This could translate to the author of an exclave that needs RWX >>>>>>> regions getting a dirty look without leaking this permission into other enclaves. >>>>>>> >>>>>>> (In my opinion, the dirty looks are actually the best security >>>>>>> benefit of the entire concept of LSMs making RWX difficult. A >>>>>>> sufficiently creative attacker can almost always bypass W^X >>>>>>> restrictions once they’ve pwned you, but W^X makes it harder to pwn >>>>>>> you in the first place, and SELinux makes it really obvious when >>>>>>> packaging a program that doesn’t respect W^X. The upshot is that a >>>>>>> lot of programs got fixed.) >>>>>> >>>>>> I'm lost here. Dynamically linked enclaves, if running on SGX2, would need RW->RX, >> i.e. >>>>> FILE__EXECMOD on /dev/sgx/enclave. But they never need RWX, i.e. PROCESS__EXECMEM. >>>>> >>>>> Hmm. If we want to make this distinction, we need something a big richer >>>>> than my proposed callbacks. A check of the actual mprotect() / mmap() >>>>> permissions would also be needed. Specifically, allowing MAXPERM=RWX >>>>> wouldn't imply that PROT_WRITE | PROT_EXEC is allowed. >>> >>> Actually, I think we do have everything we need from an LSM perspective. >>> LSMs just need to understand that sgx_enclave_load() with a NULL vma >>> implies a transition from RW. For example, SELinux would interpret >>> sgx_enclave_load(NULL, RX) as requiring FILE__EXECMOD. >>> >>> As Cedric mentioned earlier, the host process doesn't necessarily know >>> which pages will end up RW vs RX, i.e. sgx_enclave_load(NULL, RX) >>> already has to be invoked at runtime, and when that happens, the kernel >>> can take the opportunity to change the VMAs from MAY_RW to MAY_RX. >>> >>> For simplicity in the kernel and clarity in userspace, it makes sense to >>> require an explicit ioctl() to add the to-be-EAUG'd range. That just >>> leaves us wanting an ioctl() to set the post-EACCEPT{COPY} permissions. >>> >>> E.g.: >>> >>> ioctl(<prefix>_ADD_REGION, { NULL }) /* NULL == EAUG, MAY_RW */ >>> >>> mprotect(addr, size, RW); >>> ... >>> >>> EACCEPTCOPY -> EAUG /* page fault handler */ >>> >>> ioctl(<prefix>_ACTIVATE_REGION, { addr, size, RX}) /* MAY_RX */ >>> >>> mprotect(addr, size, RX); >>> >>> ... >>> >>> And making ACTIVATE_REGION a single-shot per page eliminates the need for >>> the MAXPERMS concept (see below). >>> >>>> If we keep only one MAXPERM, wouldn't this be the current behavior of >>>> mmap()/mprotect()? >>>> >>>> To be a bit more clear, system admin sets MAXPERM upper bound in the form of >>>> FILE__{READ|WRITE|EXECUTE|EXECMOD} of /dev/sgx/enclave. Then for a >>>> process/enclave, if what it requires falls below what's allowed on >>>> /dev/sgx/enclave, then everything will just work. Otherwise, it fails in the >>>> form of -EPERM returned from mmap()/mprotect(). Please note that MAXPERM here >>>> applies to "runtime" permissions, while "initial" permissions are taken care >>>> of by security_enclave_{load|init}. "initial" permissions could be more >>>> permissive than "runtime" permissions, e.g., RX is still required for initial >>>> code pages even though system admins could disable dynamically loaded code >>>> pages by *not* giving FILE__{EXECUTE|EXECMOD}. Therefore, the "initial" >>>> mapping would still have to be done by the driver (to bypass LSM), either via >>>> a new ioctl or as part of IOC_EINIT. >>> >>> Aha! >>> >>> Starting with Cedric's assertion that initial permissions can be taken >>> directly from SECINFO: >>> >>> - Initial permissions for *EADD* pages are explicitly handled via >>> sgx_enclave_load() with the exact SECINFO permissions. >>> >>> - Initial permissions for *EAUG* are unconditionally RW. EACCEPTCOPY >>> requires the target EPC page to be RW, and EACCEPT with RO is useless. >>> >>> - Runtime permissions break down as follows: >>> R - N/A, subset of RW (EAUG) >>> W - N/A, subset of RW (EAUG) and x86 paging can't do W >>> X - N/A, subset of RX (x86 paging can't do XO) >>> RW - Handled by EAUG LSM hook (uses RW unconditionally) >>> WX - N/A, subset of RWX (x86 paging can't do WX) >>> RX - Handled by ACTIVATE_REGION >>> RWX - Handled by ACTIVATE_REGION >>> >>> In other words, if we define the SGX -> LSM calls as follows (minus the >>> file pointer and other params for brevity): >>> >>> - <prefix>_ACTIVATE_REGION(vma, perms) -> sgx_enclave_load(NULL, perms) >>> >>> - <prefix>_ADD_REGION(vma) -> sgx_enclave_load(vma, SECINFO.perms) >>> >>> - <prefix>_ADD_REGION(NULL) -> sgx_enclave_load(NULL, RW) >>> >>> then SGX and LSMs have all the information and hooks needed. The catch >>> is that the LSM semantics of sgx_enclave_load(..., RW) would need to be >>> different than normal shared memory, e.g. FILE__WRITE should *not* be >>> required, but that's ok since it's an SGX specific hook. And if for some >>> reason an LSM wanted to gate access to EAUG *without* FILE__EXECMOD, it'd >>> have the necessary information to do so. >> >> Assuming that sgx_enclave_load() is a LSM hook (probably named >> security_enclave_load() instead), then: >> >> a) Does the sigstruct file get passed to this hook in every case, even >> when vma is NULL? I think the answer is yes, just want to confirm. > > I'm confused. I'm finding it difficult to follow as well, so my questions are just an attempt to understand the latest model. > In the case of EADD (non-NULL vma), are we passing both vma and sigstruct file? If so, which file dictates allowed permissions, vma->vm_file or sigstruct, or both??? My impression was that they were going to pass both, but the sigstruct file is the target of permission checks. The vma if non-NULL would be used to determine whether PROT_EXEC is being added or was already present and whether EXECMOD needs to be checked (i.e. copy-on-write has occurred and PROT_EXEC is being added). > In the case of EAUG (NULL vma), all other parameters are constant for any given enclave. Then why do we call this same hook for every region added, assuming the hook will return the same value everytime anyway? Yes, I was wondering about that as well. > And it looks like ACTIVATE_REGION is needed only because the proposed security_enclave_load() would base its decision on the sigstruct file. An alternative is to base that decision on /dev/sgx/enclave. Of course the former has finer granularity but is that really necessary? From security perspective, only the weakest link matters. FILE__EXECMOD on a regular shared object could allow exploits of all bugs throughout the host process because code within that shared object is modifiable by not only itself but also any code within that same process. In contrast, FILE__EXECMOD on /dev/sgx/enclave only allows enclaves to modify themselves. They cannot modify each other, neither can "untrusted" code outside of enclaves modify any of them. So it doesn't look like a weaker link to me. Moreover, requiring FILE__EXECMOD on sigstruct means it could be used as a target buffer for code injection attacks. IMHO that *lowers* the security of the whole process. This is partly why I suggested separate ENCLAVE__EXECMOD and other checks below, so we can distinguish between FILE__EXECMOD versus ENCLAVE__EXECMOD on the sigstruct file. If using /dev/sgx/enclave as the target, then we don't need a separate permission per se but we lose the per-sigstruct granularity. > >> >> b) Should we use a different hook for ACTIVATE_REGION than for >> ADD_REGION or is the distinction between them irrelevant/unnecessary >> from an access control point of view? At present LSM/SELinux won't be >> able to distinguish ACTIVATE_REGION(vma, RW) from ADD_REGION(NULL) above >> since they will both invoke the same hook with the same arguments IIUC. >> Does it matter? It's ok if the answer is no, just want to confirm. >> >> c) Is there still also a separate security_enclave_init() hook that will >> be called, and if so, how does it differ and when is it called relative >> to security_enclave_load()? > > I think security_enclave_init() will always be useful, as it offers a way for LSM to implement whitelisting/blacklisting. Of course an LSM module like SELinux can look into the backing inode too. I think the hook should have a signature like: > > int security_enclave_init(struct sgx_sigstruct __user *sigstruct); > > An LSM that cares about the backing file could look into vm_file of the VMA covering the buffer, while an LSM that cares the sigstruct itself (e.g. signing key) could just look into the buffer. I'm not a fan of passing __user pointers to LSM hooks. And it certainly shouldn't be looking at the buffer since it could change between the time of check and time of use. > >> >> d) What checks were you envisioning each of these calls making? >> >> With the separate security_enclave_*() hooks, we could define and use >> new ENCLAVE__* permissions, e.g. ENCLAVE__LOAD, ENCLAVE__INIT, >> ENCLAVE__EXECUTE, ENCLAVE__EXECMEM, ENCLAVE__EXECMOD, if we want to >> distinguish these operations from regular file mmap/mprotect operations. > > I'm not sure if these ENCLAVE__* flags are an overkill, unless we want to enforce an enclave file cannot be loaded as a regular shared object or vice versa. ENCLAVE__LOAD and/or ENCLAVE__INIT would be to support whitelisting of what enclaves can be loaded/initialized by the process. That's separate from the W^X discussion. Those permissions would be between the process and either the sigstruct file or the enclave file (the consensus seemed to be the sigstruct file as the stronger/more complete binding of the enclave). We probably only need one of those two permission checks not both. ENCLAVE__EXECUTE, ENCLAVE__EXECMEM, ENCLAVE__EXECMOD would allow distinctions between host process mmap/mprotect PROT_EXEC operations (which would continue to apply FILE__EXECUTE, PROCESS__EXECMEM, and FILE__EXECMOD checks if appropriate) and the driver's setting of initial and runtime permissions (which would apply ENCLAVE__EXECUTE, ENCLAVE__EXECMEM, and ENCLAVE__EXECMOD checks if appropriate). That's particularly helpful if we are using the sigstruct or enclave file as the target of all checks instead of /dev/sgx/enclave, so that we don't have to allow FILE__EXECUTE or FILE__EXECMOD to the sigstruct file by the host process. > >> >>> >>> The userspace changes are fairly minimal: >>> >>> - For SGX1, use PROT_NONE for the initial mmap() and refactor ADD_PAGE >>> to ADD_REGION. >>> >>> - For SGX2, do an explicit ADD_REGION on the ranges to be EAUG'd, and an >>> ACTIVATE_REGION to make a region RX or R (no extra ioctl() required to >>> keep RW permissions). >>> >>> Because ACTIVATE_REGION can only be done once per page, to do *abitrary* >>> mprotect() transitions, userspace would need to set the added/activated >>> permissions to be a superset of the transitions, e.g. RW -> RX would >>> require RWX, but that's a non-issue. >>> >>> - For SGX1 it's a nop since it's impossible to change the EPCM >>> permissions, i.e. the page would need to be RWX regardless. >>> >>> - For SGX2, userspace can suck it up and request RWX to do completely >>> arbitrary transitions (working as intended), or the kernel can support >>> trimming (removing) pages from an enclave, which would allow userspace >>> to do "arbitrary" transitions by first removing the page. >>> >
Hi all- After an offline discussion with Sean yesterday, here are some updates to the user API parts of my proposal. Unfortunately, Sean convinced me that MAXPERM doesn't work the way I described it because, for SGX2, the enclave loader won't know at load time whether a given EAUG-ed page will ever be executed. So here's an update. First, here are the requrements as I see them, where EXECUTE, EXECMOD, and EXECMEM could be substituted with other rules at the LSM's discretion: - You can create a WX or RWX mapping if and only if you have EXECMEM. - To create an X mapping of an enclave page that has ever been W, you need EXECMOD. - To create an X mapping of an enclave page that came from EADD, you need EXECUTE on the source file. Optionally, we could also permit this if you have EXECMOD. And I have two design proposals. One is static and one is dynamic. To implement either one, we will probably need a new .may_mprotect vm operation, and that operation can call an LSM hook. Or we can give LSMs a way to detect that a given vm_area_struct is an enclave. As I see it, this is an implementation detail that is certainly solveable. Static proposal: EADD takes an execute_intent flag. It calls a new hook: int security_enclave_load(struct vm_area_struct *source, bool execute_intent); This hook will fail if execute_intent==true and the caller has neither EXECUTE, EXECMOD, nor EXECMEM. EAUG sets execute_intent = false. EINIT takes a sigstruct pointer. SGX can (when initially upstreamed or later on once there's demand) call a new hook: security_enclave_init(struct sigstruct *sigstruct, struct vm_area_struct *source); mmap() and mprotect() will require EXECMEM to create WX or RWX mappings. They will require EXECMOD to create RX or X mappings of an execute_intent==false page. They require no permissions in the other cases. Dynamic proposal: EADD does not take any special flags. It does something like this internally: bool execute_intent = true; int security_enclave_load(struct vm_area_struct *source, bool *execute_intent); The implementation of security_enclave_load() may set *execute_intent to false. The driver records execute_intent after the LSM is done. mmap() and mprotect() will require EXECMEM to create WX or RWX mappings. They will require EXECMOD to create RX or X mappings of an execute_intent==false page. They require no permissions in the other cases. A benefit of the static proposal is that audit failures due to a lack of EXECUTE permission are easy to implement and to understand in the lods. With the dynamic model, we can only really audit the lack of EXECMOD or EXECMEM. A benefit of the dynamic model is that we hide what is arguably a decently large wart from the API.
On 5/30/19 10:31 AM, Andy Lutomirski wrote: > Hi all- > > After an offline discussion with Sean yesterday, here are some updates > to the user API parts of my proposal. > > Unfortunately, Sean convinced me that MAXPERM doesn't work the way I > described it because, for SGX2, the enclave loader won't know at load > time whether a given EAUG-ed page will ever be executed. So here's an > update. > > First, here are the requrements as I see them, where EXECUTE, EXECMOD, > and EXECMEM could be substituted with other rules at the LSM's > discretion: > > - You can create a WX or RWX mapping if and only if you have EXECMEM. > > - To create an X mapping of an enclave page that has ever been W, you > need EXECMOD. EXECMOD to what file? The enclave file from which the page's content originated, the sigstruct file, or /dev/sgx/enclave? > - To create an X mapping of an enclave page that came from EADD, you > need EXECUTE on the source file. Optionally, we could also permit > this if you have EXECMOD. What is the "source file" i.e. the target of the check? Enclave file, sigstruct file, or /dev/sgx/enclave? > > And I have two design proposals. One is static and one is dynamic. > To implement either one, we will probably need a new .may_mprotect vm > operation, and that operation can call an LSM hook. Or we can give > LSMs a way to detect that a given vm_area_struct is an enclave. As I > see it, this is an implementation detail that is certainly solveable. > > > Static proposal: > > > EADD takes an execute_intent flag. It calls a new hook: > > int security_enclave_load(struct vm_area_struct *source, bool execute_intent); > > This hook will fail if execute_intent==true and the caller has neither > EXECUTE, EXECMOD, nor EXECMEM. EADD execute_intent flag is originally provided by whom (userspace or driver) on what basis? Which file is referenced by source->vm_file? Why trigger all three checks up front versus only checking if needed? Won't this trigger a lot of unnecessary EXECMOD and EXECMEM denials that will need to be dontaudit'd? What if there is a mismatch between execute_intent and the initial permissions? > > EAUG sets execute_intent = false. > > EINIT takes a sigstruct pointer. SGX can (when initially upstreamed > or later on once there's demand) call a new hook: > > security_enclave_init(struct sigstruct *sigstruct, struct > vm_area_struct *source); Is struct sigstruct the same as struct sgx_sigstruct in the current patches (i.e. just the sigstruct data, no file)? What file is referenced by source->vm_file (the sigstruct or the enclave or /dev/sgx/enclave)? Is this hook only for enforcing a whitelist on what enclaves can be loaded? What is the target of the check? > mmap() and mprotect() will require EXECMEM to create WX or RWX > mappings. They will require EXECMOD to create RX or X mappings of an > execute_intent==false page. They require no permissions in the other > cases. Does this occur for both setting initial permissions and runtime permissions or just runtime? Both userspace- and driver-initiated mmap/mprotect operations or just userspace-initiated ones? Does the driver use interfaces that call the mmap/mprotect hooks or lower level functions? > > > Dynamic proposal: > > > EADD does not take any special flags. It does something like this internally: > > bool execute_intent = true; > int security_enclave_load(struct vm_area_struct *source, bool > *execute_intent); > > The implementation of security_enclave_load() may set *execute_intent to false. > The driver records execute_intent after the LSM is done. On what basis does LSM decide whether to set *execute_intent? If the process lacks all three permissions? What if there is a mismatch with the initial permissions? > > mmap() and mprotect() will require EXECMEM to create WX or RWX > mappings. They will require EXECMOD to create RX or X mappings of an > execute_intent==false page. They require no permissions in the other > cases. > > > > A benefit of the static proposal is that audit failures due to a lack > of EXECUTE permission are easy to implement and to understand in the > lods. With the dynamic model, we can only really audit the lack of > EXECMOD or EXECMEM. A benefit of the dynamic model is that we hide > what is arguably a decently large wart from the API. >
On Thu, May 30, 2019 at 8:04 AM Stephen Smalley <sds@tycho.nsa.gov> wrote: > > On 5/30/19 10:31 AM, Andy Lutomirski wrote: > > Hi all- > > > > After an offline discussion with Sean yesterday, here are some updates > > to the user API parts of my proposal. > > > > Unfortunately, Sean convinced me that MAXPERM doesn't work the way I > > described it because, for SGX2, the enclave loader won't know at load > > time whether a given EAUG-ed page will ever be executed. So here's an > > update. > > > > First, here are the requrements as I see them, where EXECUTE, EXECMOD, > > and EXECMEM could be substituted with other rules at the LSM's > > discretion: > > > > - You can create a WX or RWX mapping if and only if you have EXECMEM. > > > > - To create an X mapping of an enclave page that has ever been W, you > > need EXECMOD. > > EXECMOD to what file? The enclave file from which the page's content > originated, the sigstruct file, or /dev/sgx/enclave? I leave that decision to you :) The user should need permission to do an execmod thing on an enclave, however that wants to be encoded. > > > - To create an X mapping of an enclave page that came from EADD, you > > need EXECUTE on the source file. Optionally, we could also permit > > this if you have EXECMOD. > > What is the "source file" i.e. the target of the check? Enclave file, > sigstruct file, or /dev/sgx/enclave? Enclave file -- that is, the file backing the vma from which the data is loaded. > > > > > And I have two design proposals. One is static and one is dynamic. > > To implement either one, we will probably need a new .may_mprotect vm > > operation, and that operation can call an LSM hook. Or we can give > > LSMs a way to detect that a given vm_area_struct is an enclave. As I > > see it, this is an implementation detail that is certainly solveable. > > > > > > Static proposal: > > > > > > EADD takes an execute_intent flag. It calls a new hook: > > > > int security_enclave_load(struct vm_area_struct *source, bool execute_intent); > > > > This hook will fail if execute_intent==true and the caller has neither > > EXECUTE, EXECMOD, nor EXECMEM. > > EADD execute_intent flag is originally provided by whom (userspace or > driver) on what basis? Which file is referenced by source->vm_file? Why > trigger all three checks up front versus only checking if needed? Won't > this trigger a lot of unnecessary EXECMOD and EXECMEM denials that will > need to be dontaudit'd? What if there is a mismatch between > execute_intent and the initial permissions? It's provided by userspace based on whether it thinks the data in question is enclave code. source->vm_file is the file from which the code is being loaded. I'm assuming that the user code will only set excute_intent ==true if it actually wants to execute the code, so, if there's a denial, it will be fatal. The normal case will be that the request will be granted on the basis of EXECUTE. > > > > > EAUG sets execute_intent = false. > > > > EINIT takes a sigstruct pointer. SGX can (when initially upstreamed > > or later on once there's demand) call a new hook: > > > > security_enclave_init(struct sigstruct *sigstruct, struct > > vm_area_struct *source); > > Is struct sigstruct the same as struct sgx_sigstruct in the current > patches (i.e. just the sigstruct data, no file)? What file is > referenced by source->vm_file (the sigstruct or the enclave or > /dev/sgx/enclave)? Is this hook only for enforcing a whitelist on what > enclaves can be loaded? What is the target of the check? sigstruct is just the data. source->vm_file is the file from which the sigstruct came, which could be a .sigstruct file or could be the main executable or a DSO that contains an embedded enclave. The sigstruct data is there so that an LSM (not necessarily SELinux) could check MRENCLAVE or MRSIGNER, and the source is there so that the file's label can be checked. > > > mmap() and mprotect() will require EXECMEM to create WX or RWX > > mappings. They will require EXECMOD to create RX or X mappings of an > > execute_intent==false page. They require no permissions in the other > > cases. > > Does this occur for both setting initial permissions and runtime > permissions or just runtime? Both userspace- and driver-initiated > mmap/mprotect operations or just userspace-initiated ones? Does the > driver use interfaces that call the mmap/mprotect hooks or lower level > functions? These would occur for any mmap(), mprotect(), or ioctl() that changes VMA permissions. Actually arranging for the hooks to be called is an implementation detail that might require a new .mprotect vm_operation. As an alternative, security_enclave_init() or similar could supply may_execmod and may_execmem flags to the driver, and the driver could do these checks on its own when mmap() and mprotect() happen without a new LSM callback. > > > > > > > Dynamic proposal: > > > > > > EADD does not take any special flags. It does something like this internally: > > > > bool execute_intent = true; > > int security_enclave_load(struct vm_area_struct *source, bool > > *execute_intent); > > > > The implementation of security_enclave_load() may set *execute_intent to false. > > The driver records execute_intent after the LSM is done. > > On what basis does LSM decide whether to set *execute_intent? If the > process lacks all three permissions? What if there is a mismatch with > the initial permissions? > I think it would set *execute_intent=false if the process lacks EXECUTE on source->vm_file. I'm not sure any more complexity is required. If the enclave has EXECMOD, then it will still work on the basis of the mmap/mprotect rules. > > > > mmap() and mprotect() will require EXECMEM to create WX or RWX > > mappings. They will require EXECMOD to create RX or X mappings of an > > execute_intent==false page. They require no permissions in the other > > cases. > > > > > > > > A benefit of the static proposal is that audit failures due to a lack > > of EXECUTE permission are easy to implement and to understand in the > > lods. With the dynamic model, we can only really audit the lack of > > EXECMOD or EXECMEM. A benefit of the dynamic model is that we hide > > what is arguably a decently large wart from the API. > > >
On Wed, May 29, 2019 at 10:38:06PM -0700, Xing, Cedric wrote: > > From: Christopherson, Sean J > > Sent: Tuesday, May 28, 2019 2:41 PM > > > > On Tue, May 28, 2019 at 01:48:02PM -0700, Andy Lutomirski wrote: > > > On Tue, May 28, 2019 at 1:24 PM Sean Christopherson > > > <sean.j.christopherson@intel.com> wrote: > > > > > > > > Actually, I think we do have everything we need from an LSM perspective. > > > > LSMs just need to understand that sgx_enclave_load() with a NULL vma > > > > implies a transition from RW. For example, SELinux would interpret > > > > sgx_enclave_load(NULL, RX) as requiring FILE__EXECMOD. > > > > > > You lost me here. What operation triggers this callback? And > > > wouldn't sgx_enclave_load(NULL, RX) sometimes be a transition from RO > > > or just some fresh executable zero bytes? > > > > An explicit ioctl() after EACCEPTCOPY to update the allowed permissions. > > For all intents and purposes, the EAUG'd page must start RW. Maybe a better way to phrase > > it is that at some point the page must be writable to have any value whatsover. > > EACCEPTCOPY explicitly requires the page to be at least RW. EACCEPT technically doesn't > > require RW, but a RO or RX zero page is useless. Userspace could still EACCEPT with RO or > > RX, but SGX would assume a minimum of RW for the purposes of the LSM check. > > Why is an explicit ioctl() necessary after EACCEPTCOPY? Or why is mprotect() not sufficient? Ignore this, I was trying to avoid having to add a vm_ops mprotect(), which Andy pointed out was silly. > > In theory, it's still your MAXPERM model, but with the unnecessary states removed and the > > others enforced/handled by the natural SGX transitions instead of explictly in ioctls. > > Underneath the hood the SGX driver would still need to track the MAXPERM. > > What are the "unnecessary states" removed? Andy proposed taking full RWX in MAXPERMs, but really we only need "can writes ever happen to this page", as that allows the SGX driver to avoid having to track if a page has been mapped PROT_WRITE by any VMA in any process. > I'm not sure understand the proposal fully. The whole thing looks to me like > the driver is undertaking things that should/would otherwise be done by > mmap()/mprotect() syscalls. It also imposes unnecessary restrictions on user > mode code, such as mmap(PROT_NONE), ACTIVATE_REGION can be called only once, > etc. What'd happen if ACTIVATE_REGION is called with a range spanning > multiple/partial VMAs? What'd happen if an enclave was unmapped than mapped > again? I'd say the proposal is unintuitive at least. > > In theory, if the driver can keep track of MAXPERM for all pages within an > enclave, then it could fail mmap() if the requested prot conflicts with any > page's MAXPERM within that range. Otherwise, MAXPERM could be copied into > VM_MAY* flags then mprotect() will just follow through. Wouldn't that be a > much simpler and more intuitive approach? Ignore all this, again I was trying to avoid hooking mprotect().
On Thu, May 30, 2019 at 09:14:10AM -0700, Andy Lutomirski wrote: > On Thu, May 30, 2019 at 8:04 AM Stephen Smalley <sds@tycho.nsa.gov> wrote: > > > > On 5/30/19 10:31 AM, Andy Lutomirski wrote: > > > Hi all- > > > > > > After an offline discussion with Sean yesterday, here are some updates > > > to the user API parts of my proposal. > > > > > > Unfortunately, Sean convinced me that MAXPERM doesn't work the way I > > > described it because, for SGX2, the enclave loader won't know at load > > > time whether a given EAUG-ed page will ever be executed. So here's an > > > update. > > > > > > First, here are the requrements as I see them, where EXECUTE, EXECMOD, > > > and EXECMEM could be substituted with other rules at the LSM's > > > discretion: > > > > > > - You can create a WX or RWX mapping if and only if you have EXECMEM. > > > > > > - To create an X mapping of an enclave page that has ever been W, you > > > need EXECMOD. > > > > EXECMOD to what file? The enclave file from which the page's content > > originated, the sigstruct file, or /dev/sgx/enclave? > > I leave that decision to you :) The user should need permission to do > an execmod thing on an enclave, however that wants to be encoded. But that decision dictates how the SGX API handles sigstruct. If LSMs want to associate EXECMOD with sigstruct, then SGX needs to take sigstruct early and hold a reference to the file for the lifetime of the enclave. And if we're going to do that, the whole approach of inheriting permissions from source VMAs becomes unnecessary complexity. > > > > > - To create an X mapping of an enclave page that came from EADD, you > > > need EXECUTE on the source file. Optionally, we could also permit > > > this if you have EXECMOD. > > > > What is the "source file" i.e. the target of the check? Enclave file, > > sigstruct file, or /dev/sgx/enclave? > > Enclave file -- that is, the file backing the vma from which the data is loaded. It wasn't explicitly called out in Andy's proposal(s), but the idea is that the SGX driver would effectively inherit permissions from the source VMA (EADD needs a source for the initial value of the encave page). I have two gripes with that approach: - Requires enclave builder to mark enclave pages executable in the non-enclave VMAs, which may unnecessarily require EXECMOD on the source file, or even worse, EXECMEM, and potentially increases the attack surface since the file must be executable. - Is completely unnecessary if the enclave holds a reference to the sigstruct file, as LSMs can easily apply labels to the sigstruct, e.g. EXECUTE on the sigstruct instead of EXECUTE on the source file. After the bajillion mails we've generated, AIUI we've come up with two concepts that are viable: inheriting permissions from the source VMA vs. using sigstruct as a proxy for the enclave. Andy's proposals rely on the inheritance concept. The proposal below is based on the sigstruct proxy concept. For those not familiar with SGX details, sigstruct can be used as a proxy because hardware enforces that the measurement stored in the sigstruct exactly matches the measurement generated by the enclave build process, e.g. adding a page as RX instead of R will change the measurement. Core Concepts: - FILE_{READ,WRITE,EXEC} on /dev/sgx/enclave effectively gates access to EPC. All real world enclaves will need all three permissions. - sigstruct is the proxy for enclave from an LSM perspective, e.g. SELinux can define ENCLAVE__EXECUTE and ENCLAVE__EXECMOD and apply them to the sigstruct file. - Take sigstruct at ECREATE so that ADD_REGION immediately followed by mprotect() works as expected (because SGX.mprotect() needs sigstruct to pass to security_enclave_mprotect(), see below). - SGX driver takes a reference to the backing sigstruct file if it exists so that the file can be provided to LSMs during mprotect(). - Optional: SGX driver *requires* sigstruct to be backed by file, purely to enforce userspace infrastructure is in place for LSM support. W^X handling: - mmap() to /dev/sgx/enclave only allowed with PROT_NONE, i.e. force userspace through mprotect() to simplify the kernel implementation. - Add vm_ops mprotect() ops hook (I'll refer to SGX's implementation as SGX.mprotect()) - Take explicit ALLOW_WRITE at ADD_REGION, a.k.a. EADD - ADD_REGION also used to describe EAUG region (tentatively for SGX2). - Track "can be written at some point in time (past or future)" as ALLOW_WRITE (to avoid confusiong with MAY_WRITE). A priori knowledge of writability avoids having to track/coordinate PROT_WRITE across VMAs and MMs. - SGX.mprotect() returns -EPERM if PROT_WRITE && !ALLOW_WRITE. - Add security_enclave_mprotect() LSM hook, called by SGX.mprotect(), e.g. int security_enclave_mprotect(struct file *sigstruct, unsigned long prot, bool allow_write) - Intention is that EXECMOD is required if PROT_EXEC and ALLOW_WRITE. Enclave {white,black}listing: - Optional/Future: add security_enclave_create(), invoked during SGX ECREATE ioctl(), e.g. int security_enclave_create(struct vm_area_struct *sigstruct) - If this LSM hook is implemented, having sigstruct at ECREATE allows LSMs to determine whether or not the enclave is allowed to execute before allocating EPC for the enclave, e.g. unwanted enclaves can't DoS wanted enclaves. LSM implementation possibilities: - Define ENCLAVE__EXECUTE and ENCLAVE__EXECMOD, require them on the process. Does not require sigstruct to be backed by file, but cannot achieve per-enclave granularity. - Define ENCLAVE__EXECUTE and ENCLAVE__EXECMOD, require them on the sigstruct, i.e. force sigstruct to reside in filesystem. Allows per-enclave granularity. - Reuse FILE__EXECUTE and FILE__EXECMOD on sigstruct. Likely has implications that may or may not be concerning, e.g. the sigstruct file itself is weirdly executable. - Adding ENCLAVE__EXECUTE and ENCLAVE__EXECMOD means the sigstruct, which may be emdedded in the same file as the enclave, does *not* require FILE__EXECUTE or FILE__EXECMOD, e.g. can be read-only. - LSMs can (will?) require ENCLAVE__EXECUTE and ENCLAVE__EXECMOD to effectively map an enclave, even if the process acquired the enclave via SCM_RIGHTS (enclaves are tracked by fds). This is good or bad depending on your perspective. Userspace changes: - EADD ioctl adds flags param to take ALLOW_WRITE - ECREATE ioctl takes sigstruct instead of EINIT - Initial mmap() must be PROT_NONE. - sigstruct likely needs to reside in a file (this may not affect some userspace implementations).
On Thu, May 30, 2019 at 11:01 AM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Thu, May 30, 2019 at 09:14:10AM -0700, Andy Lutomirski wrote: > > On Thu, May 30, 2019 at 8:04 AM Stephen Smalley <sds@tycho.nsa.gov> wrote: > > > > > > On 5/30/19 10:31 AM, Andy Lutomirski wrote: > > > > Hi all- > > > > > > > > After an offline discussion with Sean yesterday, here are some updates > > > > to the user API parts of my proposal. > > > > > > > > Unfortunately, Sean convinced me that MAXPERM doesn't work the way I > > > > described it because, for SGX2, the enclave loader won't know at load > > > > time whether a given EAUG-ed page will ever be executed. So here's an > > > > update. > > > > > > > > First, here are the requrements as I see them, where EXECUTE, EXECMOD, > > > > and EXECMEM could be substituted with other rules at the LSM's > > > > discretion: > > > > > > > > - You can create a WX or RWX mapping if and only if you have EXECMEM. > > > > > > > > - To create an X mapping of an enclave page that has ever been W, you > > > > need EXECMOD. > > > > > > EXECMOD to what file? The enclave file from which the page's content > > > originated, the sigstruct file, or /dev/sgx/enclave? > > > > I leave that decision to you :) The user should need permission to do > > an execmod thing on an enclave, however that wants to be encoded. > > But that decision dictates how the SGX API handles sigstruct. If LSMs > want to associate EXECMOD with sigstruct, then SGX needs to take sigstruct > early and hold a reference to the file for the lifetime of the enclave. > And if we're going to do that, the whole approach of inheriting > permissions from source VMAs becomes unnecessary complexity. > > > > > > > > - To create an X mapping of an enclave page that came from EADD, you > > > > need EXECUTE on the source file. Optionally, we could also permit > > > > this if you have EXECMOD. > > > > > > What is the "source file" i.e. the target of the check? Enclave file, > > > sigstruct file, or /dev/sgx/enclave? > > > > Enclave file -- that is, the file backing the vma from which the data is loaded. > > It wasn't explicitly called out in Andy's proposal(s), but the idea is > that the SGX driver would effectively inherit permissions from the source > VMA (EADD needs a source for the initial value of the encave page). I actually meant for it to *not* work like this. I don't want the source VMA to have to be VM_EXEC. I think the LSM should just check permissions on ->vm_file.
On Thu, May 30, 2019 at 12:20:45PM -0700, Andy Lutomirski wrote: > On Thu, May 30, 2019 at 11:01 AM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > On Thu, May 30, 2019 at 09:14:10AM -0700, Andy Lutomirski wrote: > > > Enclave file -- that is, the file backing the vma from which the data is loaded. > > > > It wasn't explicitly called out in Andy's proposal(s), but the idea is > > that the SGX driver would effectively inherit permissions from the source > > VMA (EADD needs a source for the initial value of the encave page). > > I actually meant for it to *not* work like this. I don't want the > source VMA to have to be VM_EXEC. I think the LSM should just check > permissions on ->vm_file. But if ->vm_file is NULL, i.e. the enclave is not backed by a file, then PROCESS__EXECMEM is required (or more likely, ENCLAVE__EXECMEM). In practice, it's the same net effect of using sigstruct as a proxy, i.e. *something* has to get to the file system to avoid EXECMEM. But putting the entire enclave to the filesystem seems like a heaver lift than dumping the sigstruct. And if sigstruct needs to be in the file system for security_enclave_create/init()...
On Thu, May 30, 2019 at 2:16 PM Sean Christopherson <sean.j.christopherson@intel.com> wrote: > > On Thu, May 30, 2019 at 12:20:45PM -0700, Andy Lutomirski wrote: > > On Thu, May 30, 2019 at 11:01 AM Sean Christopherson > > <sean.j.christopherson@intel.com> wrote: > > > > > > On Thu, May 30, 2019 at 09:14:10AM -0700, Andy Lutomirski wrote: > > > > Enclave file -- that is, the file backing the vma from which the data is loaded. > > > > > > It wasn't explicitly called out in Andy's proposal(s), but the idea is > > > that the SGX driver would effectively inherit permissions from the source > > > VMA (EADD needs a source for the initial value of the encave page). > > > > I actually meant for it to *not* work like this. I don't want the > > source VMA to have to be VM_EXEC. I think the LSM should just check > > permissions on ->vm_file. > > But if ->vm_file is NULL, i.e. the enclave is not backed by a file, > then PROCESS__EXECMEM is required (or more likely, ENCLAVE__EXECMEM). > If ->vm_file is NULL, then I think some privilege is needed. I suppose the policy could have a new lesser permission EXECUNTRUSTED which is like EXECMOD but you can't modify it. I'm not convinced this is particular important.
On Thu, May 30, 2019 at 02:23:07PM -0700, Andy Lutomirski wrote: > On Thu, May 30, 2019 at 2:16 PM Sean Christopherson > <sean.j.christopherson@intel.com> wrote: > > > > On Thu, May 30, 2019 at 12:20:45PM -0700, Andy Lutomirski wrote: > > > On Thu, May 30, 2019 at 11:01 AM Sean Christopherson > > > <sean.j.christopherson@intel.com> wrote: > > > > > > > > On Thu, May 30, 2019 at 09:14:10AM -0700, Andy Lutomirski wrote: > > > > > Enclave file -- that is, the file backing the vma from which the data is loaded. > > > > > > > > It wasn't explicitly called out in Andy's proposal(s), but the idea is > > > > that the SGX driver would effectively inherit permissions from the source > > > > VMA (EADD needs a source for the initial value of the encave page). > > > > > > I actually meant for it to *not* work like this. I don't want the > > > source VMA to have to be VM_EXEC. I think the LSM should just check > > > permissions on ->vm_file. > > > > But if ->vm_file is NULL, i.e. the enclave is not backed by a file, > > then PROCESS__EXECMEM is required (or more likely, ENCLAVE__EXECMEM). > > > > If ->vm_file is NULL, then I think some privilege is needed. I > suppose the policy could have a new lesser permission EXECUNTRUSTED > which is like EXECMOD but you can't modify it. I'm not convinced this > is particular important. Assuming MRENCLAVE generated by Graphene or any other hosting scheme are stable[1], then avoiding EXEC<whatever> means the user can effectively whitelist what enclaves are runnable by Graphene, even if the kernel doesn't implement security_enclave_create/init(). I agree that it probably isn't all that important, it's more of a "why not" argument, i.e. what is gained by not using sigstruct as a proxy? [1] What in the world is being attested if MRENCLAVE isn't stable?
Hi Andy, I'm just curious how Sean convinced you that "MAXPERM doesn't work". More specifically, I'm objecting to the statement of "the enclave loader won't know at load time whether a given EAUG-ed page will ever be executed". I'm still new to LSM so my understanding may not be correct. But I think LSM policy defines a boundary that "loosely" restricts what a process can do. By "loosely", I mean it is usually more permissive than a process needs. For example, FILE__EXECMOD basically says there are self-modifying code in a file, but it isn't specific on which pages contain self-modifying code, hence *all* pages are allowed mprotect(RW->RX) even though only a (small) subset actually need it. LSM policies are static too, as FILE__EXECMOD is given by a system admin to be associated with the disk file, instead of being requested/programmed by any processes loading/mapping that file. So I think the same rationale applies to enclaves. Your original idea of MAXPERM is the policy set forth by system admin and shall *never* change at runtime. If an enclave is dynamically linked and needs to bring in code pages at runtime, the admin needs to enable it by setting, say ENCLAVE__EXECMOD, in the sigstruct file. Then all EAUG'ed pages will receive RWX as MAXPERM. The process would then mprotect() selective pages to be RX but which exact set of pages doesn't concern LSM usually. > From: Andy Lutomirski [mailto:luto@kernel.org] > Sent: Thursday, May 30, 2019 12:21 PM > > On Thu, May 30, 2019 at 11:01 AM Sean Christopherson <sean.j.christopherson@intel.com> > wrote: > > > > On Thu, May 30, 2019 at 09:14:10AM -0700, Andy Lutomirski wrote: > > > On Thu, May 30, 2019 at 8:04 AM Stephen Smalley <sds@tycho.nsa.gov> wrote: > > > > > > > > On 5/30/19 10:31 AM, Andy Lutomirski wrote: > > > > > Hi all- > > > > > > > > > > After an offline discussion with Sean yesterday, here are some > > > > > updates to the user API parts of my proposal. > > > > > > > > > > Unfortunately, Sean convinced me that MAXPERM doesn't work the > > > > > way I described it because, for SGX2, the enclave loader won't > > > > > know at load time whether a given EAUG-ed page will ever be > > > > > executed. So here's an update. > > > > > > > > > > First, here are the requrements as I see them, where EXECUTE, > > > > > EXECMOD, and EXECMEM could be substituted with other rules at > > > > > the LSM's > > > > > discretion: > > > > > > > > > > - You can create a WX or RWX mapping if and only if you have EXECMEM. > > > > > > > > > > - To create an X mapping of an enclave page that has ever been > > > > > W, you need EXECMOD. > > > > > > > > EXECMOD to what file? The enclave file from which the page's > > > > content originated, the sigstruct file, or /dev/sgx/enclave? > > > > > > I leave that decision to you :) The user should need permission to > > > do an execmod thing on an enclave, however that wants to be encoded. > > > > But that decision dictates how the SGX API handles sigstruct. If LSMs > > want to associate EXECMOD with sigstruct, then SGX needs to take > > sigstruct early and hold a reference to the file for the lifetime of the enclave. > > And if we're going to do that, the whole approach of inheriting > > permissions from source VMAs becomes unnecessary complexity. > > > > > > > > > > > - To create an X mapping of an enclave page that came from > > > > > EADD, you need EXECUTE on the source file. Optionally, we could > > > > > also permit this if you have EXECMOD. > > > > > > > > What is the "source file" i.e. the target of the check? Enclave > > > > file, sigstruct file, or /dev/sgx/enclave? > > > > > > Enclave file -- that is, the file backing the vma from which the data is loaded. > > > > It wasn't explicitly called out in Andy's proposal(s), but the idea is > > that the SGX driver would effectively inherit permissions from the > > source VMA (EADD needs a source for the initial value of the encave page). > > I actually meant for it to *not* work like this. I don't want the source VMA to have to > be VM_EXEC. I think the LSM should just check permissions on ->vm_file. -Cedric
On Thu, May 30, 2019 at 02:48:43PM -0700, Xing, Cedric wrote: > So I think the same rationale applies to enclaves. Your original idea of > MAXPERM is the policy set forth by system admin and shall *never* change at > runtime. If an enclave is dynamically linked and needs to bring in code pages > at runtime, the admin needs to enable it by setting, say ENCLAVE__EXECMOD, in > the sigstruct file. Then all EAUG'ed pages will receive RWX as MAXPERM. The > process would then mprotect() selective pages to be RX but which exact set of > pages doesn't concern LSM usually. Because passing RWX means the enclave "requires" EXECMOD even if it never actually does a RW->RX transition. It's not broken per se, but at the very least it's decidedly odd. Dynamically detecting the EXECMOD case is not difficult and has the advantage of simplifying userspace loaders, e.g. all EAUG pages are tagged ALLOW_WRITE and the kernel takes care of the rest. I *think* auditing/learning is also messed up with a MAXPERMS approach, as mprotect() would fail (due to MAXPERMS clearing MAY_{READ,WRITE,EXEC}) before it calls security_file_mprotect(). Hooking mprotect() is the obvious workaround, but then it's looking a lot like the new proposals. In other words, the new proposals are rooted in the MAXPERMS concept, e.g. MAXPERM is effectively "I want EXECMOD", which gets distilled down to ALLOW_WRITE (or ALLOW_EXEC in Andy's proposal).
On Thu, May 30, 2019 at 02:36:01PM -0700, Sean Christopherson wrote: Good morning, I hope everyone had a pleasant weekend. > Assuming MRENCLAVE generated by Graphene or any other hosting scheme > are stable[1], then avoiding EXEC<whatever> means the user can > effectively whitelist what enclaves are runnable by Graphene, even > if the kernel doesn't implement security_enclave_create/init(). > > I agree that it probably isn't all that important, it's more of a > "why not" argument, i.e. what is gained by not using sigstruct as a > proxy? > > [1] What in the world is being attested if MRENCLAVE isn't stable? The cryptographic identity of the entity that signed the enclave and generated the SIGSTRUCT. At the risk of being the monotone in the choir, any relevant SGX security controls require verifying the identity of whoever signed the identity characteristics (SIGSTRUCT) of the image that initiates the execution of an SGX TEE. Other then verifying the initial execution image, the MRENCLAVE value isn't all that relevant. This issue is further evidenced by the fact that sealing data to an enclave uses the MRSIGNER variant of ENCLU[EGETKEY] key derivation. The current work on LSM controls seems to focus on the identity of the entity that is requesting the image to be loaded rather then who actually signed, and presumably authored, the code. As I have previously noted, with SGX2/EDMM, a platform owner may not even have any visibility into the code that an SGX TEE may ultimately load and execute. Any security relevant LSM control in this space has to focus on providing the platform owner the ability to take action based on the contents of the SIGSTRUCT of the initiating image. In addition to the identity of who is requesting the image to be loaded. Have a good week. Dr. Greg As always, Dr. G.W. Wettstein, Ph.D. Enjellic Systems Development, LLC. 4206 N. 19th Ave. Specializing in information infra-structure Fargo, ND 58102 development. PH: 701-281-1686 FAX: 701-281-3949 EMAIL: greg@enjellic.com ------------------------------------------------------------------------------ "Experience is something you don't get until just after you need it." -- Olivier
On Thu, May 30, 2019 at 07:31:14AM -0700, Andy Lutomirski wrote: > - To create an X mapping of an enclave page that came from EADD, you > need EXECUTE on the source file. Optionally, we could also permit > this if you have EXECMOD. Source file? EADD ioctl takes memory buffer in right now. > And I have two design proposals. One is static and one is dynamic. > To implement either one, we will probably need a new .may_mprotect vm > operation, and that operation can call an LSM hook. Or we can give > LSMs a way to detect that a given vm_area_struct is an enclave. As I > see it, this is an implementation detail that is certainly solveable. Why VM operation and not file operation? > EADD takes an execute_intent flag. It calls a new hook: > > int security_enclave_load(struct vm_area_struct *source, bool execute_intent); > > This hook will fail if execute_intent==true and the caller has neither > EXECUTE, EXECMOD, nor EXECMEM. > > EAUG sets execute_intent = false. > > EINIT takes a sigstruct pointer. SGX can (when initially upstreamed > or later on once there's demand) call a new hook: > > security_enclave_init(struct sigstruct *sigstruct, struct > vm_area_struct *source); What is the source VMA in these callbacks? Why is @execute_intent needed anyway as a ioctl arugment and not deduced from SECINFO? /Jarkko
On Thu, May 30, 2019 at 11:04:24AM -0400, Stephen Smalley wrote: > Does this occur for both setting initial permissions and runtime permissions > or just runtime? Both userspace- and driver-initiated mmap/mprotect > operations or just userspace-initiated ones? Does the driver use interfaces > that call the mmap/mprotect hooks or lower level functions? The driver never initiates mmap() or mprotect(). /Jarkko
On Thu, May 30, 2019 at 09:14:10AM -0700, Andy Lutomirski wrote: > > What is the "source file" i.e. the target of the check? Enclave file, > > sigstruct file, or /dev/sgx/enclave? > > Enclave file -- that is, the file backing the vma from which the data > is loaded. Wonder why KVM gets away without having this given that enclaves are lot alike VMs. > It's provided by userspace based on whether it thinks the data in > question is enclave code. source->vm_file is the file from which the > code is being loaded. I'm assuming that the user code will only set > excute_intent ==true if it actually wants to execute the code, so, if > there's a denial, it will be fatal. The normal case will be that the > request will be granted on the basis of EXECUTE. AFAIK user spaces tells that already with the SECINFO flags. I don't get why we need a duplicate parameter. /Jarkko
On Thu, May 30, 2019 at 11:01:10AM -0700, Sean Christopherson wrote: > - Requires enclave builder to mark enclave pages executable in the > non-enclave VMAs, which may unnecessarily require EXECMOD on the > source file, or even worse, EXECMEM, and potentially increases the > attack surface since the file must be executable. Enclave builder marks *non-enclave pages*? Not following. > W^X handling: > - mmap() to /dev/sgx/enclave only allowed with PROT_NONE, i.e. force > userspace through mprotect() to simplify the kernel implementation. > - Add vm_ops mprotect() ops hook (I'll refer to SGX's implementation > as SGX.mprotect()) > - Take explicit ALLOW_WRITE at ADD_REGION, a.k.a. EADD > - ADD_REGION also used to describe EAUG region (tentatively for SGX2). > - Track "can be written at some point in time (past or future)" as > ALLOW_WRITE (to avoid confusiong with MAY_WRITE). A priori knowledge > of writability avoids having to track/coordinate PROT_WRITE across > VMAs and MMs. Still not sure why you want to use vm_ops instead of file_operations. The approach I've been proposing earlier in this email thread before these new proposals can be summarized from hook perspective as: - Allow mmap() only before ECREATE and require it to be size of the ELRANGE (ECREATE ioctl would check this). This would be with PROT_NONE. - Disallow mprotect() before EINIT. Requires a new callback to file_operations like mmap() has. - After EINIT check for each mprotect() that it matches the permissions of underlying enclave pages. Disallow mmap() after EINIT. /Jarkko
On Thu, May 30, 2019 at 02:36:01PM -0700, Sean Christopherson wrote: > Assuming MRENCLAVE generated by Graphene or any other hosting scheme are > stable[1], then avoiding EXEC<whatever> means the user can effectively > whitelist what enclaves are runnable by Graphene, even if the kernel > doesn't implement security_enclave_create/init(). > > I agree that it probably isn't all that important, it's more of a "why > not" argument, i.e. what is gained by not using sigstruct as a proxy? > > [1] What in the world is being attested if MRENCLAVE isn't stable? If I've understood correctly, Graphene uses a single loader enclave that loads the executable in. /Jarkko
On Mon, Jun 03, 2019 at 11:54:05PM +0300, Jarkko Sakkinen wrote: > On Thu, May 30, 2019 at 09:14:10AM -0700, Andy Lutomirski wrote: > > > What is the "source file" i.e. the target of the check? Enclave file, > > > sigstruct file, or /dev/sgx/enclave? > > > > Enclave file -- that is, the file backing the vma from which the data > > is loaded. > > Wonder why KVM gets away without having this given that enclaves are > lot alike VMs. From a memory management perspective, VMs are not at all like enclaves. An enclave is an extension of its host, i.e. runs in the same address. This isn't strictly necessary, e.g. an enclave could run in a sandbox process, but even then the enclave will be running with the kernel's standard page tables. A VM is a essentially an opaque blob of data that gets loaded into memory. KVM builds a completely different set of page tables for the VM, the VM has it's own file system (or perhaps doesn't have a file system at all), etc... Ignoring Spectre and L1TF, the VM is contained to its own world. There are a lot of ways for a userspace VMM to expose things beyond raw memory, but doing so requires the appropriate permissions. And practically speaking, all traditional VMs will effectively need RWX memory, i.e. Qemu (or any other userspace VMM) would be required to have EXECMEM permissions, which would be a net negative for security. > > It's provided by userspace based on whether it thinks the data in > > question is enclave code. source->vm_file is the file from which the > > code is being loaded. I'm assuming that the user code will only set > > excute_intent ==true if it actually wants to execute the code, so, if > > there's a denial, it will be fatal. The normal case will be that the > > request will be granted on the basis of EXECUTE. > > AFAIK user spaces tells that already with the SECINFO flags. I don't > get why we need a duplicate parameter. Please read through the RFC, I think it address a lot of your questions. Hopefully that will help us avoid some thrash.
On Mon, Jun 3, 2019 at 1:54 PM Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> wrote: > > On Thu, May 30, 2019 at 09:14:10AM -0700, Andy Lutomirski wrote: > > > What is the "source file" i.e. the target of the check? Enclave file, > > > sigstruct file, or /dev/sgx/enclave? > > > > Enclave file -- that is, the file backing the vma from which the data > > is loaded. > > Wonder why KVM gets away without having this given that enclaves are > lot alike VMs. > I would argue it's because access to /dev/kvm means you can execute whatever you code you want in a VM. I don't see how this is avoidable. On the other hand, it would be nice for SGX to not imply this same sort of "execute anything" right, especially since, unlike KVM, SGX is not a sandbox.
On Mon, Jun 03, 2019 at 02:23:36PM -0700, Sean Christopherson wrote: > Please read through the RFC, I think it address a lot of your questions. > Hopefully that will help us avoid some thrash. I promise to read it through with detail albeit I just said that as a patch set it is broken :-) Internals still need documentation tho... /Jarkko