# Enclave Forking
## High Level Overview
* Parent enclave
* SM will mark the parent’s EPM as read-only. (this will be replaced by the snapshot)
* SM will allocate a new read-only
* Child Enclave
* SM will create an EPM region with the same number of EPM as the parent and initialize the enclave with initial registers ($satp, etc)
* All pointers will still point to the parent process
* SM allocates metadata to mark the child enclave as child of parent enclave
* Each child will receive a read-only PMP entry for parent upon enclave switch
* Upon write access to the parent’s EPM pages, SM will get a access fault
* Store/AMO access fault -- Exception code 7
* SM will copy the page
* Note: SM may have to copy multiple pages (multiple forks) to other enclaves
* I.E. Parent enclave spawns forks multiple child enclaves and parent enclave edits a page -- must copy the page to each child it spawns
* Code can be handled in: `void machine_page_fault(uintptr_t *regs, uintptr_t dummy, uintptr_t mepc)`
## Code Modifications
```
/* enclave metadata */
struct enclave
{
...
enclave_id eid; //enclave id
...
enclave_id parent_eid; // parent eid
struct list children; // list of children eids
}
```
* Each enclave will now have a parent eid, `parent_eid`
* Default value will be `-1`
* Any enclave that is forked will have its parent eid set.
* Each enclave will now have a list of its children enclaves, `children`
* This isn't completely necessary
* Might be useful to keep track of children eids if we wanted to implement `wait`
* Upon `context_switch_to_enclave`, we also want to flip the PMP regions of the parent enclave
* The PMP regions will grant `read` access to the child enclave, but NOT write.
* Ensures that the host still cannot access this read-only image.
* PMP region will be marked by `enclave[eid].regions`
* The SM is responsible for ensuring fork is correct, so the EID of the of parent enclave is guaranteed to be correct.
`void machine_page_fault(uintptr_t *regs, uintptr_t dummy, uintptr_t mepc)`
`void pmp_trap(uintptr_t* regs, uintptr_t mcause, uintptr_t mepc)`
* `int cpu_get_enclave_id()`
* Use this to find the enclave which caused the access fault
* In the parent, we need to keep track of all dirty pages since we called our snapshot, we can keep track of this through the access fault.
* Can find the `dram_base` of the parent enclave by doing `enclaves[parent_eid].dram_base`
* Check if the faulting address, `mepc`, is within the parent enclave's EPM and that it is a `write` access
* If it is in the parent's EPM, copy the page into the child's EPM.
* This can be done easily by keeping track of the EPM base of each enclave `runtime_pa_params.dram_base`
```
offset = (mepc & PAGE_MASK) - runtime_pa_params.dram_base;
memcpy(enclaves[cpu_get_enclave_id()].dram_base + offset, enclaves[parent_eid].dram_base + offset, PG_SIZE);
```
* If the `mepc` isn't in the parent's enclave then handle the trap normally
```
/*
Creates a copy of the parent enclave (enclave that called fork)
and returns the child enclave's eid.
*/
enclave_id enclave_fork(uintptr_t* regs, enclave_id eid, int load_parameters){
enclave_id child_eid = -1;
// Copy registers (at time of fork), parameters (dram_base, runtime_base, runtime_entry, etc)
// Create new enclave with new enclave eid
child_eid = copy_enclave(regs, load_parameters);
// Add parent enclave's PMP region to child enclave
// Set child enclave's parent eid to correct eid
// Add child to parent enclave's children list
register_child(child_eid);
return child_eid;
}
```
* We also set `$a0` register to be `0` for the child's process since the return value of the child process upon 'fork()' should be `0`
# Non-determinism (post-init measurement)
* After the child enclave is initialized and created, it might receive input parameters (i.e. in serverless computing, we can provision the enclave with some function)
* At the time of `fork()`, the parent enclave's state may have changed (i.e. stack and heap are modified) since its initial measurement
* We need to be able to attest some intermediete state of the enclave
* Can we use the initial measurement of the parent enclave to help speed up intermeidete hash?
## Solution 1: Selective Measuring
* The enclave application can designate a region which will not be measured.
* For enclave applications that are non-deterministic, we can mark sections in code that won't be included in the intermediete measurement
* This can be a simple ELF section, which must be marked and cannot be modified after the enclave application is initialized in the SM
* This can be implemented by using a bit in the PTE to mark a page to NOT be included in the measurement
* Initial measurement still contains the non-measured section
* Initial measurment ensures the non-measured sections are zero'd out
* Upon any intermediete measurement, marked pages will not be considered.
* This ensures that no adversary modifies the binary when it is initialized by the SM
* Cons:
* An adversary can exploit this by putting injected code in the non-measured pages.
* Adversary can inject code in pages marked as non-measured and create a valid attestation report.
* We can mitigate this with standard buffer overflow protections (i.e. canaries, bounds checking, etc)
## Solution 2: Measure Log between Host and Enclave
* Non-determinism is from outside the EPM (i.e. inputs given from the host)
* SM keeps track of logs sent and received from an external source (i.e. host, enclave, etc.)
* All messages recieved/sent from external source will be logged and included in measurement.
* What about concurrency, where communication between several external sources are in non-determinsitic order?
* We can use a Multi-Set Hash (https://people.csail.mit.edu/devadas/pubs/mhashes.pdf)
* Hashes do not depend on the order of the set
* Cons:
* This doesn't solve non-determinism from the program itself (i.e. calling on a RNG within an enclave)
* We assume ALL non-determinism is from communication