[RFC,v2,5/5] security/selinux: Add enclave_load() implementation
diff mbox series

Message ID 20190606021145.12604-6-sean.j.christopherson@intel.com
State New
Headers show
Series
  • security: x86/sgx: SGX vs. LSM
Related show

Commit Message

Sean Christopherson June 6, 2019, 2:11 a.m. UTC
The goal of selinux_enclave_load() is to provide a facsimile of the
existing selinux_file_mprotect() and file_map_prot_check() policies,
but tailored to the unique properties of SGX.

For example, an enclave page is technically backed by a MAP_SHARED file,
but the "file" is essentially shared memory that is never persisted
anywhere and also requires execute permissions (for some pages).

The basic concept is to require appropriate execute permissions on the
source of the enclave for pages that are requesting PROT_EXEC, e.g. if
an enclave page is being loaded from a regular file, require
FILE__EXECUTE and/or FILE__EXECMOND, and if it's coming from an
anonymous/private mapping, require PROCESS__EXECMEM since the process
is essentially executing from the mapping, albeit in a roundabout way.

Note, FILE__READ and FILE__WRITE are intentionally not required even if
the source page is backed by a regular file.  Writes to the enclave page
are contained to the EPC, i.e. never hit the original file, and read
permissions have already been vetted (or the VMA doesn't have PROT_READ,
in which case loading the page into the enclave will fail).

Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
---
 security/selinux/hooks.c | 69 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

Comments

Stephen Smalley June 7, 2019, 9:16 p.m. UTC | #1
On 6/5/19 10:11 PM, Sean Christopherson wrote:
> The goal of selinux_enclave_load() is to provide a facsimile of the
> existing selinux_file_mprotect() and file_map_prot_check() policies,
> but tailored to the unique properties of SGX.
> 
> For example, an enclave page is technically backed by a MAP_SHARED file,
> but the "file" is essentially shared memory that is never persisted
> anywhere and also requires execute permissions (for some pages).
> 
> The basic concept is to require appropriate execute permissions on the
> source of the enclave for pages that are requesting PROT_EXEC, e.g. if
> an enclave page is being loaded from a regular file, require
> FILE__EXECUTE and/or FILE__EXECMOND, and if it's coming from an
> anonymous/private mapping, require PROCESS__EXECMEM since the process
> is essentially executing from the mapping, albeit in a roundabout way.
> 
> Note, FILE__READ and FILE__WRITE are intentionally not required even if
> the source page is backed by a regular file.  Writes to the enclave page
> are contained to the EPC, i.e. never hit the original file, and read
> permissions have already been vetted (or the VMA doesn't have PROT_READ,
> in which case loading the page into the enclave will fail).
> 
> Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
> ---
>   security/selinux/hooks.c | 69 ++++++++++++++++++++++++++++++++++++++++
>   1 file changed, 69 insertions(+)
> 
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 3ec702cf46ca..3c5418edf51c 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -6726,6 +6726,71 @@ static void selinux_bpf_prog_free(struct bpf_prog_aux *aux)
>   }
>   #endif
>   
> +#ifdef CONFIG_INTEL_SGX
> +int selinux_enclave_load(struct vm_area_struct *vma, unsigned long prot)
> +{
> +	const struct cred *cred = current_cred();
> +	u32 sid = cred_sid(cred);
> +	int ret;
> +
> +	/* SGX is supported only in 64-bit kernels. */
> +	WARN_ON_ONCE(!default_noexec);
> +
> +	/* Only executable enclave pages are restricted in any way. */
> +	if (!(prot & PROT_EXEC))
> +		return 0;

prot/PROT_EXEC or vmflags/VM_EXEC

> +
> +	/*
> +	 * The source page is exectuable, i.e. has already passed SELinux's

executable

> +	 * checks, and userspace is not requesting RW->RX capabilities.

Is it requesting W->X or WX?

> +	 */
> +	if ((vma->vm_flags & VM_EXEC) && !(prot & PROT_WRITE))
> +		return 0;
> +
> +	/*
> +	 * The source page is not executable, or userspace is requesting the
> +	 * ability to do a RW->RX conversion.  Permissions are required as
> +	 * follows, in order of increasing privelege:
> +	 *
> +	 * EXECUTE - Load an executable enclave page without RW->RX intent from
> +	 *           a non-executable vma that is backed by a shared mapping to
> +	 *           a regular file that has not undergone COW.

Shared mapping or unmodified private file mapping

> +	 *
> +	 * EXECMOD - Load an executable enclave page without RW->RX intent from
> +	 *           a non-executable vma that is backed by a shared mapping to
> +	 *           a regular file that *has* undergone COW.

modified private file mapping (write to shared mapping won't trigger 
COW; it would have been checked by FILE__WRITE earlier)

> +	 *
> +	 *         - Load an enclave page *with* RW->RX intent from a shared
> +	 *           mapping to a regular file.
> +	 *
> +	 * EXECMEM - Load an exectuable enclave page from an anonymous mapping.

executable

> +	 *
> +	 *         - Load an exectuable enclave page from a private file, e.g.

executable

> +	 *           from a shared mapping to a hugetlbfs file.
> +	 *
> +	 *         - Load an enclave page *with* RW->RX intent from a private

W->X or WX?

> +	 *           mapping to a regular file.
> +	 *
> +	 * Note, this hybrid EXECMOD and EXECMEM behavior is intentional and
> +	 * reflects the nature of enclaves and the EPC, e.g. EPC is effectively
> +	 * a non-persistent shared file, but each enclave is a private domain
> +	 * within that shared file, so delegate to the source of the enclave.
> +	 */
> +	if (vma->vm_file && !IS_PRIVATE(file_inode(vma->vm_file) &&
> +	    ((vma->vm_flags & VM_SHARED) || !(prot & PROT_WRITE)))) {
> +		if (!vma->anon_vma && !(prot & PROT_WRITE))
> +			ret = file_has_perm(cred, vma->vm_file, FILE__EXECUTE);
> +		else
> +			ret = file_has_perm(cred, vma->vm_file, FILE__EXECMOD);
> +	} else {
> +		ret = avc_has_perm(&selinux_state,
> +				   sid, sid, SECCLASS_PROCESS,
> +				   PROCESS__EXECMEM, NULL);
> +	}
> +	return ret;
> +}
> +#endif
> +
>   struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = {
>   	.lbs_cred = sizeof(struct task_security_struct),
>   	.lbs_file = sizeof(struct file_security_struct),
> @@ -6968,6 +7033,10 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
>   	LSM_HOOK_INIT(bpf_map_free_security, selinux_bpf_map_free),
>   	LSM_HOOK_INIT(bpf_prog_free_security, selinux_bpf_prog_free),
>   #endif
> +
> +#ifdef CONFIG_INTEL_SGX
> +	LSM_HOOK_INIT(enclave_load, selinux_enclave_load),
> +#endif
>   };
>   
>   static __init int selinux_init(void)
>
Sean Christopherson June 10, 2019, 4:46 p.m. UTC | #2
On Fri, Jun 07, 2019 at 05:16:01PM -0400, Stephen Smalley wrote:
> On 6/5/19 10:11 PM, Sean Christopherson wrote:
> >The goal of selinux_enclave_load() is to provide a facsimile of the
> >existing selinux_file_mprotect() and file_map_prot_check() policies,
> >but tailored to the unique properties of SGX.
> >
> >For example, an enclave page is technically backed by a MAP_SHARED file,
> >but the "file" is essentially shared memory that is never persisted
> >anywhere and also requires execute permissions (for some pages).
> >
> >The basic concept is to require appropriate execute permissions on the
> >source of the enclave for pages that are requesting PROT_EXEC, e.g. if
> >an enclave page is being loaded from a regular file, require
> >FILE__EXECUTE and/or FILE__EXECMOND, and if it's coming from an
> >anonymous/private mapping, require PROCESS__EXECMEM since the process
> >is essentially executing from the mapping, albeit in a roundabout way.
> >
> >Note, FILE__READ and FILE__WRITE are intentionally not required even if
> >the source page is backed by a regular file.  Writes to the enclave page
> >are contained to the EPC, i.e. never hit the original file, and read
> >permissions have already been vetted (or the VMA doesn't have PROT_READ,
> >in which case loading the page into the enclave will fail).
> >
> >Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
> >---
> >  security/selinux/hooks.c | 69 ++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 69 insertions(+)
> >
> >diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> >index 3ec702cf46ca..3c5418edf51c 100644
> >--- a/security/selinux/hooks.c
> >+++ b/security/selinux/hooks.c
> >@@ -6726,6 +6726,71 @@ static void selinux_bpf_prog_free(struct bpf_prog_aux *aux)
> >  }
> >  #endif
> >+#ifdef CONFIG_INTEL_SGX
> >+int selinux_enclave_load(struct vm_area_struct *vma, unsigned long prot)
> >+{
> >+	const struct cred *cred = current_cred();
> >+	u32 sid = cred_sid(cred);
> >+	int ret;
> >+
> >+	/* SGX is supported only in 64-bit kernels. */
> >+	WARN_ON_ONCE(!default_noexec);
> >+
> >+	/* Only executable enclave pages are restricted in any way. */
> >+	if (!(prot & PROT_EXEC))
> >+		return 0;
> 
> prot/PROT_EXEC or vmflags/VM_EXEC
> 
> >+
> >+	/*
> >+	 * The source page is exectuable, i.e. has already passed SELinux's
> 
> executable
> 
> >+	 * checks, and userspace is not requesting RW->RX capabilities.
> 
> Is it requesting W->X or WX?

Hmm, good point.  I'll reword the "requesting RW->RX" and "RW->RX intent"
phrases to make it clear that we don't actually know whether userspace
intends to do W->X or WX, and I'll also expand the "Note, this hybrid
EXECMOD and EXECMEM behavior" comment to explain that existing checks
won't prevent WX.

> >+	 */
> >+	if ((vma->vm_flags & VM_EXEC) && !(prot & PROT_WRITE))
> >+		return 0;
> >+
> >+	/*
> >+	 * The source page is not executable, or userspace is requesting the
> >+	 * ability to do a RW->RX conversion.  Permissions are required as
> >+	 * follows, in order of increasing privelege:
> >+	 *
> >+	 * EXECUTE - Load an executable enclave page without RW->RX intent from
> >+	 *           a non-executable vma that is backed by a shared mapping to
> >+	 *           a regular file that has not undergone COW.
> 
> Shared mapping or unmodified private file mapping

Doh, messed that up.  Thanks!

> >+	 *
> >+	 * EXECMOD - Load an executable enclave page without RW->RX intent from
> >+	 *           a non-executable vma that is backed by a shared mapping to
> >+	 *           a regular file that *has* undergone COW.
> 
> modified private file mapping (write to shared mapping won't trigger COW; it
> would have been checked by FILE__WRITE earlier)

Same mental error.  Will fix.

> >+	 *
> >+	 *         - Load an enclave page *with* RW->RX intent from a shared
> >+	 *           mapping to a regular file.
> >+	 *
> >+	 * EXECMEM - Load an exectuable enclave page from an anonymous mapping.
> 
> executable
> 
> >+	 *
> >+	 *         - Load an exectuable enclave page from a private file, e.g.
> 
> executable

At least I'm consistent.

> >+	 *           from a shared mapping to a hugetlbfs file.
> >+	 *
> >+	 *         - Load an enclave page *with* RW->RX intent from a private
> 
> W->X or WX?
>
> >+	 *           mapping to a regular file.
> >+	 *
> >+	 * Note, this hybrid EXECMOD and EXECMEM behavior is intentional and
> >+	 * reflects the nature of enclaves and the EPC, e.g. EPC is effectively
> >+	 * a non-persistent shared file, but each enclave is a private domain
> >+	 * within that shared file, so delegate to the source of the enclave.
> >+	 */
> >+	if (vma->vm_file && !IS_PRIVATE(file_inode(vma->vm_file) &&
> >+	    ((vma->vm_flags & VM_SHARED) || !(prot & PROT_WRITE)))) {
> >+		if (!vma->anon_vma && !(prot & PROT_WRITE))
> >+			ret = file_has_perm(cred, vma->vm_file, FILE__EXECUTE);
> >+		else
> >+			ret = file_has_perm(cred, vma->vm_file, FILE__EXECMOD);
> >+	} else {
> >+		ret = avc_has_perm(&selinux_state,
> >+				   sid, sid, SECCLASS_PROCESS,
> >+				   PROCESS__EXECMEM, NULL);
> >+	}
> >+	return ret;
> >+}
> >+#endif
> >+
> >  struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = {
> >  	.lbs_cred = sizeof(struct task_security_struct),
> >  	.lbs_file = sizeof(struct file_security_struct),
> >@@ -6968,6 +7033,10 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
> >  	LSM_HOOK_INIT(bpf_map_free_security, selinux_bpf_map_free),
> >  	LSM_HOOK_INIT(bpf_prog_free_security, selinux_bpf_prog_free),
> >  #endif
> >+
> >+#ifdef CONFIG_INTEL_SGX
> >+	LSM_HOOK_INIT(enclave_load, selinux_enclave_load),
> >+#endif
> >  };
> >  static __init int selinux_init(void)
> >
>
Jarkko Sakkinen June 17, 2019, 4:38 p.m. UTC | #3
On Wed, Jun 05, 2019 at 07:11:45PM -0700, Sean Christopherson wrote:
> The goal of selinux_enclave_load() is to provide a facsimile of the
> existing selinux_file_mprotect() and file_map_prot_check() policies,
> but tailored to the unique properties of SGX.
> 
> For example, an enclave page is technically backed by a MAP_SHARED file,
> but the "file" is essentially shared memory that is never persisted
> anywhere and also requires execute permissions (for some pages).
> 
> The basic concept is to require appropriate execute permissions on the
> source of the enclave for pages that are requesting PROT_EXEC, e.g. if
> an enclave page is being loaded from a regular file, require
> FILE__EXECUTE and/or FILE__EXECMOND, and if it's coming from an
> anonymous/private mapping, require PROCESS__EXECMEM since the process
> is essentially executing from the mapping, albeit in a roundabout way.
> 
> Note, FILE__READ and FILE__WRITE are intentionally not required even if
> the source page is backed by a regular file.  Writes to the enclave page
> are contained to the EPC, i.e. never hit the original file, and read
> permissions have already been vetted (or the VMA doesn't have PROT_READ,
> in which case loading the page into the enclave will fail).
> 
> Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>

In the end of the day, the main problem with this patch is that the
existing LSM hooks are generic. I don't we can have specific hooks
for proprietary hardware.

/Jarkko

Patch
diff mbox series

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 3ec702cf46ca..3c5418edf51c 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6726,6 +6726,71 @@  static void selinux_bpf_prog_free(struct bpf_prog_aux *aux)
 }
 #endif
 
+#ifdef CONFIG_INTEL_SGX
+int selinux_enclave_load(struct vm_area_struct *vma, unsigned long prot)
+{
+	const struct cred *cred = current_cred();
+	u32 sid = cred_sid(cred);
+	int ret;
+
+	/* SGX is supported only in 64-bit kernels. */
+	WARN_ON_ONCE(!default_noexec);
+
+	/* Only executable enclave pages are restricted in any way. */
+	if (!(prot & PROT_EXEC))
+		return 0;
+
+	/*
+	 * The source page is exectuable, i.e. has already passed SELinux's
+	 * checks, and userspace is not requesting RW->RX capabilities.
+	 */
+	if ((vma->vm_flags & VM_EXEC) && !(prot & PROT_WRITE))
+		return 0;
+
+	/*
+	 * The source page is not executable, or userspace is requesting the
+	 * ability to do a RW->RX conversion.  Permissions are required as
+	 * follows, in order of increasing privelege:
+	 *
+	 * EXECUTE - Load an executable enclave page without RW->RX intent from
+	 *           a non-executable vma that is backed by a shared mapping to
+	 *           a regular file that has not undergone COW.
+	 *
+	 * EXECMOD - Load an executable enclave page without RW->RX intent from
+	 *           a non-executable vma that is backed by a shared mapping to
+	 *           a regular file that *has* undergone COW.
+	 *
+	 *         - Load an enclave page *with* RW->RX intent from a shared
+	 *           mapping to a regular file.
+	 *
+	 * EXECMEM - Load an exectuable enclave page from an anonymous mapping.
+	 *
+	 *         - Load an exectuable enclave page from a private file, e.g.
+	 *           from a shared mapping to a hugetlbfs file.
+	 *
+	 *         - Load an enclave page *with* RW->RX intent from a private
+	 *           mapping to a regular file.
+	 *
+	 * Note, this hybrid EXECMOD and EXECMEM behavior is intentional and
+	 * reflects the nature of enclaves and the EPC, e.g. EPC is effectively
+	 * a non-persistent shared file, but each enclave is a private domain
+	 * within that shared file, so delegate to the source of the enclave.
+	 */
+	if (vma->vm_file && !IS_PRIVATE(file_inode(vma->vm_file) &&
+	    ((vma->vm_flags & VM_SHARED) || !(prot & PROT_WRITE)))) {
+		if (!vma->anon_vma && !(prot & PROT_WRITE))
+			ret = file_has_perm(cred, vma->vm_file, FILE__EXECUTE);
+		else
+			ret = file_has_perm(cred, vma->vm_file, FILE__EXECMOD);
+	} else {
+		ret = avc_has_perm(&selinux_state,
+				   sid, sid, SECCLASS_PROCESS,
+				   PROCESS__EXECMEM, NULL);
+	}
+	return ret;
+}
+#endif
+
 struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = {
 	.lbs_cred = sizeof(struct task_security_struct),
 	.lbs_file = sizeof(struct file_security_struct),
@@ -6968,6 +7033,10 @@  static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
 	LSM_HOOK_INIT(bpf_map_free_security, selinux_bpf_map_free),
 	LSM_HOOK_INIT(bpf_prog_free_security, selinux_bpf_prog_free),
 #endif
+
+#ifdef CONFIG_INTEL_SGX
+	LSM_HOOK_INIT(enclave_load, selinux_enclave_load),
+#endif
 };
 
 static __init int selinux_init(void)