Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions enclave/server/memory_rss_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package server

import "testing"

func TestParseVmRSSBytes(t *testing.T) {
const status = `Name: go-enclave
Umask: 0022
State: R (running)
VmPeak: 2100000 kB
VmSize: 2000000 kB
VmRSS: 1234560 kB
RssAnon: 1200000 kB
Threads: 42
`
if got, want := parseVmRSSBytes([]byte(status)), uint64(1234560)*1024; got != want {
t.Fatalf("parseVmRSSBytes = %d, want %d", got, want)
}

cases := map[string][]byte{
"missing line": []byte("Name:\tx\nVmSize:\t100 kB\n"),
"empty input": []byte(""),
"malformed": []byte("VmRSS:\tnotanumber kB\n"),
"no value": []byte("VmRSS:\n"),
}
for name, in := range cases {
if got := parseVmRSSBytes(in); got != 0 {
t.Errorf("%s: parseVmRSSBytes = %d, want 0", name, got)
}
}
}
47 changes: 43 additions & 4 deletions enclave/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import (
"log"
"net"
"net/http"
"os"
"runtime/metrics"
"strconv"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -235,10 +238,11 @@ func (s *enclaveServer) attestPublicKeys(dataToAttest [32]byte) ([]byte, error)
}

// handleMemory handles the GET /memory endpoint. It reports the enclave process's
// memory usage as read from the Go runtime, rounded to the nearest megabyte. The
// megabyte granularity is deliberate: it is a coarse operational signal, and the
// rounding limits the resolution of any memory-based side channel into the
// confidential workload.
// memory usage, rounded to the nearest megabyte: UsedMB from the Go runtime, and
// RSSMB (resident set size) which also covers native allocations like the
// wasmtime WASM linear memory. The megabyte granularity is deliberate: it is a
// coarse operational signal, and the rounding limits the resolution of any
// memory-based side channel into the confidential workload.
func (s *enclaveServer) handleMemory(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, fmt.Sprintf("method not allowed: %v", r.Method), http.StatusMethodNotAllowed)
Expand All @@ -247,6 +251,7 @@ func (s *enclaveServer) handleMemory(w http.ResponseWriter, r *http.Request) {

resp := types.MemoryEstimateResponse{
UsedMB: bytesToMB(readRuntimeMemoryBytes()),
RSSMB: bytesToMB(readProcessRSSBytes()),
}

w.Header().Set("Content-Type", "application/json")
Expand All @@ -271,6 +276,40 @@ func readRuntimeMemoryBytes() uint64 {
return 0
}

// readProcessRSSBytes returns the enclave process's resident set size in bytes,
// from /proc/self/status (VmRSS). Unlike readRuntimeMemoryBytes, which sees only
// Go-runtime-mapped memory, RSS includes native allocations such as the wasmtime
// WASM linear memory that dominate the enclave's footprint under load. Returns 0
// if unavailable (e.g. non-Linux dev builds, where /proc is absent).
func readProcessRSSBytes() uint64 {
data, err := os.ReadFile("/proc/self/status")
if err != nil {
return 0
}
return parseVmRSSBytes(data)
}

// parseVmRSSBytes extracts VmRSS from /proc/<pid>/status content and returns it
// in bytes (the file reports kB). Returns 0 if the line is absent or malformed.
func parseVmRSSBytes(status []byte) uint64 {
for _, line := range strings.Split(string(status), "\n") {
rest, ok := strings.CutPrefix(line, "VmRSS:")
if !ok {
continue
}
fields := strings.Fields(rest) // e.g. ["123456", "kB"]
if len(fields) < 1 {
return 0
}
kb, err := strconv.ParseUint(fields[0], 10, 64)
if err != nil {
return 0
}
return kb * 1024
}
return 0
}

// bytesToMB rounds a byte count to the nearest megabyte.
func bytesToMB(b uint64) uint64 {
const mb = 1024 * 1024
Expand Down
5 changes: 5 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,4 +446,9 @@ type MemoryEstimateResponse struct {
// UsedMB is all memory mapped by the Go runtime, rounded to the nearest
// megabyte.
UsedMB uint64 `json:"usedMB"`
// RSSMB is the enclave process's resident set size (VmRSS), rounded to the
// nearest megabyte. Unlike UsedMB it includes native allocations outside the
// Go runtime, notably the wasmtime WASM linear memory, so it reflects the
// enclave's true footprint under load. 0 if unavailable (e.g. non-Linux).
RSSMB uint64 `json:"rssMB"`
}
Loading