Split Trusted and Untrusted Code

What you will learn

This lesson covers:

  • Application partitioning
  • Trusted and untrusted code
  • ECALL and OCALL-style boundaries
  • Input validation
  • Iago attacks
  • Interface minimization
  • Error handling
  • Data ownership

The application boundary is an API

A TEE boundary is not only a memory boundary. It is also an application interface.

The trusted component receives requests from untrusted code and may request services from the untrusted host. Every crossing is an attack surface.

A safe TEE application should expose operations, not raw internal memory.

A good interface might look like:

`` sign_approved_transaction(transaction) ``

A dangerous interface might look like:

`` read_enclave_memory(address, length) ``

Trusted and untrusted components

A typical process-enclave design contains:

Untrusted host

Responsible for:

  • Starting the enclave
  • Receiving network traffic
  • File and network I/O
  • Scheduling
  • Storage
  • User interface
  • Resource management

Trusted application

Responsible for:

  • Secret processing
  • Cryptographic operations
  • Policy enforcement
  • Input validation
  • State validation
  • Sensitive output generation

The objective is not to move the entire application inside the enclave. The objective is to move the security-critical logic inside while keeping the interface small.

ECALL and OCALL

Intel SGX uses the terms:

  • ECALL: Entry from the untrusted application into the enclave
  • OCALL: Call from enclave code to an untrusted host function

Intel's Enclave Definition Language describes trusted and untrusted function declarations and generates code that marshals data across the boundary.

Other platforms use different names, but the same idea applies:

  • Trusted code receives an external request.
  • Trusted code may request an external service.
  • Parameters cross between trusted and untrusted memory.

Minimize entry points

Every entry point should have:

  • Clear purpose
  • Defined input format
  • Maximum input size
  • Authentication requirement
  • Authorization rule
  • Error behaviour
  • Rate limit
  • State transition
  • Output classification

Instead of exposing many small internal operations, expose a few high-level operations.

``` Bad: get_key() decrypt_block() read_plaintext() sign_hash()

Better: decrypt_and_validate_document(encrypted_document) sign_transaction_if_policy_allows(transaction) ```

The second design keeps key material and intermediate plaintext inside the trusted boundary.

Validate every input

The trusted application should assume that the host can provide:

  • Incorrect lengths
  • Invalid pointers
  • Duplicate requests
  • Reordered requests
  • Malformed encodings
  • Oversized values
  • Invalid signatures
  • Stale timestamps
  • Unexpected state
  • Incorrect file contents
  • Fake network responses

Validation should happen inside the trusted boundary.

``` function process_request(request_bytes): if length(request_bytes) > MAX_REQUEST_SIZE: reject

request = strict_decode(request_bytes)

if request.version != SUPPORTED_VERSION: reject

if not verify_signature(request): reject

if request.nonce already used: reject

if not policy_allows(request.operation): reject

return execute(request) ```

Copy, validate, then use

Data stored in untrusted memory may change while the trusted application is reading it.

A safer pattern is:

1. Check the declared size. 2. Copy the input into trusted memory. 3. Validate the copied value. 4. Use only the trusted copy. 5. Clear sensitive temporary buffers.

This reduces time-of-check to time-of-use problems across the boundary.

Safe request-processing flow across a TEE boundary, showing copying into trusted memory, validation, authentication, authorization, freshness checks, execution, and sanitized output

Do not trust host pointers

Raw pointers from the host should not be treated as stable trusted references.

The host may:

  • Change the referenced memory
  • Reuse the buffer
  • Provide an overlapping range
  • Provide a range outside expected memory
  • Trigger integer overflow in length calculations

Use bounded copying and checked arithmetic.

``` if length > MAX: reject

if pointer + length overflows: reject

copy_from_untrusted(pointer, length, trusted_buffer) ```

Iago attacks

An Iago attack occurs when an untrusted operating system returns malicious values from a system call or service interface.

The trusted application may assume that the operating system follows normal rules. The malicious host breaks those assumptions.

Examples include:

  • Returning an overlapping memory address
  • Returning a fake file offset
  • Returning a manipulated time value
  • Replaying network responses
  • Returning inconsistent file metadata
  • Claiming a write succeeded when it did not
  • Providing a malicious random value
💡
The original Iago-attack research showed why a conventional system-call interface can be unsafe when the operating system is treated as hostile — the trusted application must never assume the OS follows standard contract rules.
Common Iago attack behaviors showing how an untrusted host can return malicious system-call and service results to a TEE, with defensive validation patterns.

Trusted time is difficult

The host clock may be untrusted. A malicious host may:

  • Move time backward
  • Move time forward
  • Freeze time
  • Return inconsistent timestamps

Do not use host time as the only protection for:

  • Certificate validity
  • Auction deadlines
  • Key expiration
  • Replay prevention
  • Rate limits
  • State versions

Possible alternatives include:

  • Trusted external time service
  • Monotonic counter
  • Consensus timestamp
  • Signed time response
  • Multiple independent time sources

Each option introduces new trust.

Trusted randomness

Randomness used for keys, nonces, and challenges must come from a suitable source.

Do not allow an untrusted host to select a cryptographic nonce without validation.

The TEE may use:

  • Hardware random-number generator
  • Protected operating-system randomness in a confidential VM
  • Multiple entropy sources
  • Deterministic random generator seeded inside the TEE

The exact source depends on the platform.

Avoid secret-dependent error messages

The application may accidentally reveal information through errors.

`` Error: User exists but password is incorrect Error: Decryption key was correct but document signature failed Error: First 12 bytes of token matched ``

Use uniform external errors when detailed information would leak secrets. Detailed diagnostics may be stored inside a protected debugging environment, but production logs should remain privacy-aware.

Example: TEE signing service

A signing service should not expose:

`` get_private_key() ``

It should expose:

`` sign_if_allowed(message, authorization) ``

Inside the TEE:

1. Parse the message. 2. Verify the authorization. 3. Check policy. 4. Check nonce and state. 5. Generate signature. 6. Record the action. 7. Return the signature.

The host receives the result but never receives the key.

Developer exercise

Design the interface for a private-auction enclave.

The enclave must:

  • Accept encrypted bids
  • Enforce a deadline
  • Prevent duplicate bids
  • Select a winner
  • Reveal only the result

Write:

1. Three trusted functions 2. Three host functions 3. Input rules 4. State rules 5. Output rules 6. Two malicious host behaviours

Common mistakes

**Moving every library into the trusted boundary** — Keep the TCB as small as possible. **Exposing too many entry points** — Each entry point is an attack surface. **Trusting host pointers** — Always copy into trusted memory first. **Trusting the host clock** — Use a trusted external source or a monotonic counter for time-sensitive operations. **Logging secrets outside the TEE** — All sensitive output stays inside the trusted boundary. **Returning detailed secret-dependent errors** — Use uniform external error codes. **Assuming system calls behave honestly** — Treat all host responses as potentially manipulated. **Using shared memory without copying and validation** — Never process data in place from untrusted memory.

Key takeaways

  • Treat the TEE boundary as a hostile API boundary.
  • Keep trusted code small.
  • Expose high-level operations.
  • Copy and validate all untrusted input.
  • Treat system calls and host responses as attacker-controlled.
  • Keep secret material and intermediate plaintext inside the trusted boundary.

Check your understanding

1. Why should the TEE expose operations rather than raw keys? 2. What is an OCALL? 3. Why should data be copied into trusted memory before validation? 4. What is an Iago attack? 5. Why can host-provided time be dangerous?

Answer the quiz correctly to continue →

Quiz · Multiple Choice1 / 3

What is an Iago attack, and what does it target in a TEE system?