POSIX Shared-Memory Startup: Prevent a Second Process from Using Half-Initialized State

Electing the creator of a POSIX shared-memory object does not mean the object is ready for another process to use. A successful exclusive shm_open can publish a name while its length is still zero and its synchronization objects are uninitialized. Separate creation, sizing, initialization and readiness publication, and require attachers to pass each boundary before accessing shared state.

This guide uses Linux man-pages 6.12 for POSIX shared memory and process-shared synchronization. It proposes a single-coordinator bootstrap protocol for an embedded service pair. No named shared-memory startup fixture or concurrent attacher test was executed; the sequences are design and qualification procedures, not measured deployment results.

Give one coordinator authority over the object generation

Choose an explicit owner for naming, creation, publication and retirement. Record a protocol version, layout size and generation identifier. A name such as /device_state is a rendezvous label, not proof that an existing object belongs to the currently intended service generation.

The shm_open contract makes O_CREAT|O_EXCL an atomic existence-and-creation decision. A loser receives EEXIST and must follow the attacher protocol. It must not fall back to O_TRUNC or reinitialize the object in place merely because its own startup wants a fresh record.

A newly created object has zero length. Its newly allocated bytes are zero-initialized after sizing, but zero bytes do not establish a valid pthread mutex, semaphore, application header or initialized state machine. Treat physical allocation and semantic construction as different stages.

Shared-memory startup progresses from exclusive creator election and a zero-length named object to construction and external READY publication; attachers validate extent, identity and synchronization before use.
Original bootstrap model. Creation election, initialized storage and readiness are separate boundaries.

Size before mapping or accessing the region

The creator checks shm_open, sets the required extent with ftruncate, checks that result, and creates a shared mapping with the intended permissions. Every failure before publication leaves the generation unavailable to peers. Clean up only the object this creator actually owns.

An attacher first opens the intended object and checks its size with fstat. It must reject an extent smaller than the required header and bounds-check the complete declared layout before accessing any offset. Do not map a desired size and dereference it before confirming the backing object is large enough.

The mmap reference documents SIGBUS for access to a mapped page beyond the underlying file’s end. A mapping call succeeding is therefore not proof that every requested byte is backed by the object. Concurrent truncation is also incompatible with readers that still rely on the previous extent; forbid it through the lifecycle protocol.

Construct synchronization before publishing readiness

Initialize each synchronization primitive through its supported API and check its return value. A mutex shared by different processes needs the PTHREAD_PROCESS_SHARED attribute and storage accessible to all participants. Robustness, if required, is another independent attribute and does not make uninitialized mutex bytes safe to lock.

Do not use the mutex inside the new mapping to serialize its own first initialization. Both processes would need the mutex to exist before they could coordinate creating it. Bootstrap needs an already established control channel, an externally owned synchronization object or another explicitly verified construction protocol.

For the proposed coordinator design, initialize the entire region privately from the peers’ perspective, then send a READY message over an established authenticated control channel. Include the generation and expected layout identity. Attachers receive that publication before using the mapping’s synchronization primitives, then acquire the appropriate initialized lock before reading mutable payload state.

Make publication meaningful across processes

A plain shared integer named ready is not automatically a synchronization protocol. A volatile qualifier does not establish atomic interprocess publication or order all preceding payload writes. If the implementation uses shared atomics, qualify their process-shared and lock-free/platform contract explicitly and use the required ordering. This article does not supply a portable atomic bootstrap implementation.

An out-of-band readiness handshake avoids making a not-yet-initialized in-memory primitive the first gate. The control-channel protocol must still bind READY to the exact object generation and provide the required synchronization. Passing the prepared descriptor through the channel can reduce ambiguous name lookup, but descriptor passing itself needs complete admission and ownership handling.

After publication, layout checks remain mandatory. Validate magic/version, extent, offsets, element counts and arithmetic before following data references. Separate ABI compatibility from application content validation. A process using another libc layout for embedded pthread objects is not qualified merely because the application header has the same version number.

Observed startup state Attacher decision
Name exists, size zero or too small Not ready; do not access the intended structure.
Expected size, no valid publication Construction may be incomplete; wait or fail under a bounded policy.
READY for another generation Reject stale coordination data and resolve the intended object.
Matching publication and compatible validated layout Use initialized synchronization before mutable-state access.

Use offsets and retire stale objects safely

Different processes may map the same object at different virtual addresses. Store validated offsets or indexes for shared references rather than raw process-local pointers, unless the design has a separately proven fixed-address contract. Check offset plus length for overflow and containment within the mapped extent.

The shared-memory overview describes kernel persistence and the separate close, unmap and unlink operations. Closing the descriptor after mapping does not remove the mapping. Unlinking removes the rendezvous name while existing mappings can remain alive; recreating the same name produces a distinct object.

If the creator dies before READY, do not let an arbitrary attacher take over construction merely because a timeout elapsed. The coordinator must establish that the old generation is retired and that no live owner can resume writing. A replacement generation should have an unambiguous identity, with old participants prevented from publishing into the new session.

Test each visibility boundary

In a disposable setup, pause the creator immediately after exclusive creation, after sizing, after partial primitive initialization and immediately before READY. Start an attacher at each pause and require a bounded not-ready outcome without touching uninitialized state. Then complete construction and require normal attachment to the matching generation.

Add stale named objects, incompatible layout versions, malformed offsets, creator exit before publication and delayed READY messages from an old generation. Verify that failures never trigger destructive truncation of an object another participant owns. Keep cleanup accounting for names, descriptors and mappings separate.

The acceptance artifact is a creator/attacher state trace that shows exactly when shared bytes become usable. A name, a successful mmap and an all-zero page are three observations; none alone proves that the shared service state has been constructed and published safely.

Similar Posts