Skip to content

[bug] LSSHM_PAGESIZE hardcoded to 8K breaks OLS on 16 KB-page kernels (Apple Silicon, Raspberry Pi 5) #486

Description

@Dudematters

ols-1.9.0-16k-page.patch

Summary

openlitespeed segfaults during startup on Linux systems where the kernel page size is 16 KB (Apple Silicon under Asahi Linux, Raspberry Pi 5 with the 16K-page kernel, some ARM server kernels). Root cause: LSSHM_PAGESIZE is hardcoded to 0x2000 (8 KB) in src/shm/lsshmtypes.h, and the cache-manager shared-memory code consequently uses 8 KB-aligned file offsets in mmap() calls. On a 16 KB-page kernel, an 8 KB-aligned offset that isn't also 16 KB-aligned makes mmap() return EINVAL (per POSIX — file offset must be a multiple of the system page size). OLS's error-handling path then dereferences a munmap()'d pointer, producing SIGSEGV.

Fix is a two-line constant change. The on-disk SHM format is unaffected (it's organised in LSSHM_SHM_UNITSIZE = 1 KB chunks, independent of the page constant).

Environment

  • Hardware: Apple Mac Mini M2 (Apple Silicon, ARMv8.5-A)
  • OS: Ubuntu Asahi 24.04 LTS Server
  • Kernel: 6.17.0-1001-asahi-arm
  • Page size: getconf PAGESIZE16384
  • OLS: 1.9.0, built from source (./build.sh -s -o OFF)
  • Toolchain: gcc 13.3, glibc 2.39

Also reproduces with the LiteSpeed apt-repo binary (apt install openlitespeed from https://repo.litespeed.sh) — identical crash, same EINVAL mmap signature — so it isn't a build-config issue.

Reproduction

  1. Install OLS on any Linux system with a 16 KB-page kernel.
  2. Run openlitespeed -t (config test) or systemctl start lshttpd.
  3. Process exits with SIGSEGV during cache-manager shared-memory initialization, before any listener binds. systemd shows status=11/SEGV and restart-loops.

Diagnostic evidence

strace -f /usr/local/lsws/bin/openlitespeed -t (relevant tail):

openat(AT_FDCWD, "/usr/local/lsws/cachedata/.cacheman.shm", O_RDWR|O_CREAT|O_EXCL, 0640) = 8
fallocate(8, 0, 0, 40960)               = 0
mmap(NULL, 2097152, PROT_NONE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xffff923d0000
mmap(0xffff92400000, 40960, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, 8, 0) = 0xffff92400000
fallocate(8, 0, 40960, 8192)            = 0
mmap(0xffff9240a000, 8192, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, 8, 0xa000) = -1 EINVAL (Invalid argument)
unlinkat(AT_FDCWD, "/usr/local/lsws/cachedata/.cacheman.shm", 0) = 0
munmap(0xffff92400000, 1048576)         = 0
close(8)                                = 0
munmap(0xffff92ba0000, 8192)            = 0
close(9)                                = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xffff92ba0028} ---
+++ killed by SIGSEGV (core dumped) +++

The failing call:

mmap(0xffff9240a000, 8192, ..., MAP_SHARED|MAP_FIXED, 8, 0xa000) = -1 EINVAL

File offset 0xa000 (40960 = 5 × 8 KB) is NOT a multiple of 16 KB (40960 ÷ 16384 = 2.5). On 4 KB-page systems the same offset is also 4 KB-aligned and the call succeeds; on 16 KB-page systems the kernel correctly rejects it. See mmap(2): "The offset argument is constrained to be a multiple of the page size as returned by sysconf(_SC_PAGE_SIZE)."

The binary itself is fine — readelf -l shows LOAD segments with Align 0x10000 (64 KB), more than sufficient for any current page size:

  LOAD  0x0000000000000000  ...  R E    0x10000
  LOAD  0x0000000000804b60  ...  RW     0x10000

So the issue is purely in runtime use of mmap, not in the binary's load-time mapping.

Root cause

In src/shm/lsshmtypes.h:

#define LSSHM_PAGESIZE          0x2000  // min pagesize 8k
#define LSSHM_PAGEMASK          0xFFFFE000

LSSHM_PAGESIZE is the "page size" that LsShm uses internally for file extension and mmap() offsets. It's hardcoded to 8 KB, with the implicit assumption that 8 KB is always ≥ the system page size. That assumption breaks on 16 KB-page kernels.

The relevant uses (all runtime, no on-disk format dependency):

  • src/shm/lsshm.cpp:111LsShmSize_t LsShm::s_iPageSize = LSSHM_PAGESIZE; (runtime variable, drives mmap offsets)
  • src/shm/lsshmlock.cpp:65static LsShmSize_t s_iPageSize = LSSHM_PAGESIZE;
  • src/shm/addrmap.cpp:192start_offset = start_offset & ~(LSSHM_PAGESIZE - 1); (alignment math, auto-follows the constant)
  • src/shm/lsshmtypes.h:125#define LSSHM_INITSIZE LSSHM_PAGESIZE (auto-follows)

Note src/shm/lsshm.cpp:56 already uses getpagesize() correctly inside the posix_fallocate EINVAL/EOPNOTSUPP fallback path — so the code is partially page-size aware. The main code path just uses the hardcoded constant.

Fix

Two-line patch:

--- a/src/shm/lsshmtypes.h
+++ b/src/shm/lsshmtypes.h
@@ -109,8 +109,8 @@
 #define LSSHM_VER_TYPE          \
     (((sizeof(LsShmOffset_t)<<4) | sizeof(LsShmXSize_t)) & (0xff)) // 8 bits
 
-#define LSSHM_PAGESIZE          0x2000  // min pagesize 8k
-#define LSSHM_PAGEMASK          0xFFFFE000
+#define LSSHM_PAGESIZE          0x4000  // min pagesize 16k (for 16K-page kernels)
+#define LSSHM_PAGEMASK          0xFFFFC000
 #define LSSHM_MAXNAMELEN        12      // only 11 characters.

This bumps the internal page size to 16 KB, which is a multiple of both 4 KB and 16 KB system page sizes — so the fix is safe on existing 4 KB-page x86_64 and ARM64 systems as well as on the new 16 KB-page kernels.

After patching, rebuilding, and clearing any pre-existing cachedata/*.shm and cachedata/*.lock files (they're not on-disk-incompatible, but the failed-init runs left them orphaned), OLS starts cleanly on Apple Silicon and serves requests normally.

A more thorough long-term fix

Making LSSHM_PAGESIZE dynamic — std::max((size_t)0x2000, (size_t)sysconf(_SC_PAGESIZE)) at startup — would future-proof against hypothetical 32 KB or 64 KB-page kernels. That's a more invasive change since several uses are compile-time constants (alignment masks, struct field expressions), so it isn't a strict one-liner. The constant bump above is sufficient for all currently-shipping Linux distributions.

Affected platforms

Any Linux kernel configured with a 16 KB page size, including:

  • Apple Silicon (M1/M2/M3/M4) on Asahi Linux (Fedora Asahi, Ubuntu Asahi, etc.)
  • Raspberry Pi 5 with the 16K-page kernel variant (CONFIG_ARM64_16K_PAGES=y)
  • ARM server SKUs configured for 16 KB pages
  • Some custom embedded kernels

Worth noting that the prebuilt aarch64 binaries shipped via the LiteSpeed apt repo (https://repo.litespeed.sh) carry the same bug; rebuilding those after the source fix would close the loop for users who don't build from source.

Verification post-patch

Apple M2 / Ubuntu Asahi 24.04 / kernel 6.17:

$ sudo /usr/local/lsws/bin/lswsctrl status
litespeed is running with PID 70929.

$ sudo ss -tlnp | grep -E ':8088|:7080'
LISTEN  0  4096  0.0.0.0:8088  0.0.0.0:*  users:(("litespeed",pid=...))
LISTEN  0  4096  0.0.0.0:7080  0.0.0.0:*  users:(("litespeed",pid=...))

$ curl -I http://localhost:8088/
HTTP/1.1 200 OK
server: LiteSpeed
...

Clean startup, listener binding, 200 response. No SEGV in journalctl -u lshttpd.


Happy to submit a pull request with the patch if useful, or you can apply it directly — it's two lines and the diff above is in unified format ready to patch -p1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions