October 18, 2023·4 min read·engineeringvalkeyredissecuritysockets

Redis sets Unix socket permissions before listening

Redis 7.2.2 fixes a startup race that briefly exposed a Unix socket under unintended permissions. The repair establishes configured permissions before accepting connections.

A correct final permission mode does not prove that a service was protected throughout startup. Redis's October security fix addresses a short interval in which its Unix socket could listen before the server applied the configured permissions. Another local process could race that initialization and establish a connection that the intended mode should have prevented.

The 7.2 release notes date Redis 7.2.2 to October 18 and identify CVE-2023-45145. The advisory, published the same day, ties exposure to use of a permissive umask. This fix changes when the socket's permissions take effect relative to the point at which it starts listening.

I have used Redis since its early releases, and local sockets are a useful deployment option when the client and server share a host. Their filesystem permissions need to be part of the connection boundary from the moment that boundary becomes reachable. A configuration that becomes restrictive only after listening has started leaves a different policy during initialization.

The old helper listened before returning

The fixing commit shows the original order. The Unix server function constructed its socket address and passed it to a shared listener helper. That helper called bind() and then listen(). Only after it returned did the Unix-specific function call chmod() with the configured mode.

This ordering created a gap between publication of a listening endpoint and installation of the intended access restriction. The gap could be short while still being observable by another scheduled process. Its duration did not need to resemble a normal application request interval to matter.

The umask condition explains why the configured mode alone did not describe the whole exposure. Before the explicit permission change, creation-time permissions reflected the environment in which Redis started. A permissive initial state could permit a connection during the gap even if the eventual configured mode was restrictive.

The advisory describes an otherwise unauthorized connection, not a claim that the socket remained permanently misconfigured. Looking at the file after successful startup could therefore show the intended permissions while missing the earlier race. This is an initialization-order problem with a correct-looking final state. A review of the final configuration file alone cannot establish the order of system calls. The patch supplies that missing evidence by showing where binding, permission setup, and listening occur within the same helper. Their order is the property that changes.

The new helper receives the permission mode

The patch adds a mode argument to the shared listener helper. After binding, it checks whether the address belongs to the local socket family and whether a nonzero permission mode was supplied. It then applies chmod() before calling listen().

The Unix server function passes its configured mode into that helper. Its old post-listen permission change disappears. TCP callers pass zero for the added argument, so the Unix filesystem permission operation remains conditional on the appropriate address family and setting.

That is a small structural change with a specific effect. The helper that starts listening now has the information needed to perform the permission step first. Leaving permissions with the caller made it too easy for the shared helper to publish the endpoint before the caller could constrain it.

The patch should be described at that scope. It reorders permission setup relative to listening. It does not introduce a new authentication protocol, change Redis command permissions, or make every possible socket-creation error part of a new security subsystem.

The feature is explicitly configured

The 7.2.2 configuration file says there is no default Unix socket path. Redis does not listen on one unless a path is specified. The affected setup is therefore a deployment using this transport, not every server merely because it runs on a Unix-like host.

An illustrative configuration follows the same option names shown in the versioned file. Its path is a deployment example whose directory must already have the intended ownership and access policy.

unixsocket /run/redis/redis.sock
unixsocketperm 700

The mode expresses the desired permissions for the socket path. In the repaired startup sequence, the explicit permission step precedes listening. The example does not disable a separately configured TCP listener or establish application-level Redis credentials.

That distinction keeps transport restrictions separate from command authorization. Restricting who can reach a local socket is one policy. Deciding what a connected Redis user may execute is another. This vulnerability concerns the former boundary during startup.

The documented workarounds constrain the initial exposure

The advisory lists three workaround approaches for installations unable to upgrade. They can disable Unix sockets, start Redis with a restrictive umask, or place the socket file in a protected directory. Each approach changes whether another local process can reach the endpoint during the problematic interval.

Disabling the transport removes that endpoint. A restrictive umask narrows its initial permissions before the later explicit change. A protected directory restricts access to the socket path through the enclosing filesystem boundary. These choices address different parts of the setup and have different deployment consequences.

The patched branches named by the advisory are 7.2.2, 7.0.14, and 6.2.14. It traces the issue back to 2.6.0-RC1. That long history describes the affected initialization pattern, while the named patch versions identify the fixes provided in the listed branches.

Redis 7.2.2 also fixes a separate WAITAOF timeout case involving a newly created AOF whose rewrite could not immediately start without write traffic. That entry concerns persistence progress rather than socket permissions. The release groups both corrections, but the startup race has its own transport-specific conditions.

I regard the patch as a useful statement about initialization contracts. The restriction needed to govern a connection belongs before the operation that makes the connection possible. Here that means passing the configured mode into the binding helper and applying it before listen(), so the intended local access policy is present when the socket becomes a listening endpoint.