-
Notifications
You must be signed in to change notification settings - Fork 223
Description
Hello, I'm running nginx and lighttpd over Gramine-SGX to host an HTTP server. I'm facing the following bugs, and point out the code that needs to be patched (may be helpful to other users):
nginx
Shared memory and file offset issues between parent and child processes. so we need to patch nginx to make it work.
1. accept_mutex not work
Nginx uses accept_mutex to prevent the thundering herd problem, where multiple worker processes simultaneously try to handle new requests, sometimes causing performance issues. Related codes as below:
- Shared memory is used to create ngx_accept_mutex.
static ngx_int_t
ngx_event_module_init(ngx_cycle_t *cycle)
{
if (ngx_shm_alloc(&shm) != NGX_OK) {
return NGX_ERROR;
}
shared = shm.addr;
ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
ngx_accept_mutex.spin = (ngx_uint_t) -1;
if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
cycle->lock_file.data)
!= NGX_OK)
{
return NGX_ERROR;
}
}- Nginx uses
accept_mutexto prevent worker race to serve.
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
if (ngx_use_accept_mutex) {
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;
} else {
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
/* here to process request */
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
}2. Connection limit not work
Worker processes share connection statistics via shared memory. However, due to Gramine's limitations with shared memory, the number of connections might exceed the configured limit.
Each process can ensure the connection limit isn't exceeded, but we lose the overall limit control.
(ngx_stream_limit_conn_handler has same problem as ngx_http_limit_conn_handler)
static ngx_int_t
ngx_http_limit_conn_handler(ngx_http_request_t *r)
{
for (i = 0; i < lccf->limits.nelts; i++) {
node = ngx_http_limit_conn_lookup(&ctx->sh->rbtree, &key, hash);
if (node == NULL) {
/* Share connection statistics using shared memory. */
node = ngx_slab_alloc_locked(ctx->shpool, n);
} else {
/* Process when the number of connections exceeds the limit */
if ((ngx_uint_t) lc->conn >= limits[i].conn) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
ngx_log_error(lccf->log_level, r->connection->log, 0,
"limiting connections%s by zone \"%V\"",
lccf->dry_run ? ", dry run," : "",
&limits[i].shm_zone->shm.name);
ngx_http_limit_conn_cleanup_all(r->pool);
r->main->limit_conn_status = NGX_HTTP_LIMIT_CONN_REJECTED;
return lccf->status_code;
}
lc->conn++;
}
}
}3. Cache limit not work
Workers should share cache limit, but it fail to do so.
static ngx_msec_t
ngx_http_file_cache_manager(void *data)
{
for ( ;; ) {
ngx_shmtx_lock(&cache->shpool->mutex);
size = cache->sh->size;
count = cache->sh->count;
watermark = cache->sh->watermark;
ngx_shmtx_unlock(&cache->shpool->mutex);
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
"http file cache size: %O c:%ui w:%i",
size, count, (ngx_int_t) watermark);
if (size < cache->max_size && count < watermark) {
if (!cache->min_free) {
break;
}
free = ngx_fs_available(cache->path->name.data);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
"http file cache free: %O", free);
if (free > cache->min_free) {
break;
}
}
}
}Attacker can hide log
In nginx, after the parent process opens a log file, it forks worker processes that should share a file offset (or called cursor/pos) for coordinated writing.
But this offset is isolated by Enclave in Gramine-SGX, workers worked as it had exclusive access to the log file, so we can see worker B‘s log overwrite worker A's log, due to file offset can not be correctly shared.
2025/08/14 13:38:38 [debug] 1#0: bind() 0.0.0.0:80 #6
2025/08/14 13:38:38 [notice] 1#0: using the "epoll" event method
2025/08/14 13:38:38 [debug] 1#0: counter: 0000000004EF5080, 1
2025/08/14 13:38:38 [notice] 1#0: nginx/1.27.4
2025/08/14 13:38:38 [notice] 1#0: built by clang 18.1.8 ([email protected]:LeoneChen/llvm-project.git 50d2edd53bfb24337d312bb44d491fc4a6bb5bc5)
2025/08/14 13:38:38 [notice] 1#0: OS: Linux 3.10.0
2025/08/14 13:38:38 [notice] 1#0: getrlimit(RLIMIT_NOFILE): 900:65536
2025/08/14 13:38:38 [debug] 1#0: write: 7, 000000000678B650, 2, 0
2025/08/14 13:38:38 [debug] 1#0: setproctitle: "nginx: master process /home/leone/SGXDiff/install-dir/"
2025/08/14 13:38:38 [notice] 1#0: start worker processes
2025/08/14 13:38:38 [debug] 1#0: channel 3:7
2025/08/14 13:38:38 [notice] 1#0: start worker process 2
2025/08/14 13:38:38 [debug] 1#0: channel 8:9
2025/08/14 13:38:38 [debug] 3#0: add cleanup: 0000000004E2025/02025/08/14 13:38:38 [debug] 3#0: malloc: 0000000008507EB0:8
2025/08/14 13:38:38 [debug] 3#0: notify eventfd: 11
2025/08/14 13:38:38 [debug] 3#0: testing the EPOLLRDHUP flag: success
2025/08/14 13:38:38 [debug] 3#0: malloc: 00000000084F4C90:6144
2025/08/14 13:38:38 [debug] 3#0: malloc: 0000000004EFC440:114688
2025/08/14 13:38:38 [debug] 3#0: malloc: 0000000004F18450:49152
2025/08/14 13:38:38 [debug] 3#0: malloc: 0000000004F24460:49152
2025/08/14 13:38:38 [debug] 3#0: epoll add event: fd:9 op:1 ev:00002001
2025/08/14 13:38:38 [debug] 3#0: setproctitle: "nginx: worker process"
2025/08/14 13:38:38 [debug] 3#0: worker cycle
2025/08/14 13:38:38 [debug] 3#0: accept mutex locked
2025/08/14 13:38:32025/08/14 13:38:54 [debug] 2#0: epoll: fd:6 ev:0001 d:0000000004EFC440
##### We can see here worker B's log overwrites worker A's log. #####
2025/08/14 13:38:54 [debug] 2#0: post event 0000000004F18450
0: epoll: fd:6 ev:0001 d:0000000004EFC440lighttpd
1. ETag mismatch
Gramine-SGX uses the file path to calculate st_ino, because it is difficult for userspace to get inode information.
But in lighttpd, it uses st_ino to calculate ETag. When a file is renamed in lighttpd, st_ino and ETag may be different from the original file, thus invalidating the cache.
st_ino is used in many places in lighttpd, so we need to patch them all.
void
http_etag_create (buffer * const etag, const struct stat * const st, const int flags)
{
if (flags & ETAG_USE_INODE)
x[len++] = (uint64_t)st->st_ino;
http_etag_remix(etag, (char *)x, len << 3);
}