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

Reply via email to