Expand description

A restricted channel to pass data from signal handler.

When trying to communicate data from signal handler to the outside world, one can use an atomic variable (as it doesn’t lock, so it can be made async-signal-safe). But this won’t work for larger data.

This module provides a channel that can be used for that purpose. It is used by certain exfiltrators, but can be used as building block for custom actions. In general, this is not a ready-made end-user API.

How does it work

Each channel has a fixed number of slots and two queues (one for empty slots, one for full slots). A signal handler takes a slot out of the empty one, fills it and passes it into the full one. Outside of signal handler, it can take the value out of the full queue and return the slot to the empty queue.

The queues are implemented as bit-encoded indexes of the slots in the storage. The bits are stored in an atomic variable.

Note that the algorithm allows for a slot to be in neither queue (when it is being emptied or filled).

Fallible allocation of a slot

It is apparent that allocation of a new slot can fail (there’s nothing in the empty slot). In such case, there’s no way to send the new value out of the handler (there’s no way to safely wait for a slot to appear, because the handler can be blocking the thread that is responsible for emptying them). But that’s considered acceptable ‒ even the kernel collates the same kinds of signals together if they are not consumed by application fast enough and there are no free slots exactly because some are being filled, emptied or are full ‒ in particular, the whole system will yield a signal.

This assumes that separate signals don’t share the same buffer and that there’s only one reader (using multiple readers is still safe, but it is possible that all slots would be inside the readers, but already empty, so the above argument would not hold).

Structs

A restricted async-signal-safe channel