Skip to content

fix leak of uninitialized stack memory in CfGetFile refusal replies - #6408

Merged
larsewi merged 1 commit into
cfengine:masterfrom
Dhanu-dynamic:cfgetfile-zero-reply-buffer
Oct 15, 2026
Merged

larsewi merged 1 commit into
cfengine:masterfrom
Dhanu-dynamic:cfgetfile-zero-reply-buffer

Conversation

@Dhanu-dynamic

Copy link
Copy Markdown
Contributor

CfGetFile() in cf-serverd/server_common.c has a local reply buffer:

char sendbuffer[CF_BUFSIZE + 256], filename[CF_BUFSIZE - 128];

On its two early-return paths, access refused by TransferRights() and open() failure, it writes only the short CF_FAILEDSTR status string with snprintf() and then sends args->buf_size bytes of the buffer back to the client:

snprintf(sendbuffer, CF_BUFSIZE, "%s", CF_FAILEDSTR);
...
SendSocketStream(ConnectionInfoSocket(conn_info), sendbuffer, args->buf_size);
...                                      (or TLSSend() on the TLS protocol)

args->buf_size is the block size from the client's "GET <buf_size> " request, validated only to be within 1..CF_BUFSIZE. CF_FAILEDSTR is 59 bytes, so a client that asks for a file it is not granted, or one that cannot be opened, with buf_size near CF_BUFSIZE makes the server, running as root, transmit up to about 4 KB of uninitialized stack per request. The bytes past the status string are whatever the frame held from earlier request handling.

The successful-transfer loop already memsets the buffer before each read, and CfEncryptGetFile() reports refusals with FailedTransfer() rather than sending from the buffer, so only these GET refusal/error replies leak.

Zero-initialize sendbuffer at declaration. Valid transfers are unchanged; the refusal and error replies now pad with zeros instead of stack contents.

CfGetFile() in cf-serverd/server_common.c has a local reply buffer:

    char sendbuffer[CF_BUFSIZE + 256], filename[CF_BUFSIZE - 128];

On its two early-return paths, access refused by TransferRights() and
open() failure, it writes only the short CF_FAILEDSTR status string with
snprintf() and then sends args->buf_size bytes of the buffer back to the
client:

    snprintf(sendbuffer, CF_BUFSIZE, "%s", CF_FAILEDSTR);
    ...
    SendSocketStream(ConnectionInfoSocket(conn_info), sendbuffer, args->buf_size);
    ...                                      (or TLSSend() on the TLS protocol)

args->buf_size is the block size from the client's "GET <buf_size> <file>"
request, validated only to be within 1..CF_BUFSIZE. CF_FAILEDSTR is 59
bytes, so a client that asks for a file it is not granted, or one that
cannot be opened, with buf_size near CF_BUFSIZE makes the server, running
as root, transmit up to about 4 KB of uninitialized stack per request. The
bytes past the status string are whatever the frame held from earlier
request handling.

The successful-transfer loop already memsets the buffer before each read,
and CfEncryptGetFile() reports refusals with FailedTransfer() rather than
sending from the buffer, so only these GET refusal/error replies leak.

Zero-initialize sendbuffer at declaration. Valid transfers are unchanged;
the refusal and error replies now pad with zeros instead of stack contents.

Changelog: Title
Signed-off-by: Dhanraj E <dhanraj@labs.digiscrypt.com>

@larsewi larsewi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 🚀

@larsewi

larsewi commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

@cf-bottom Jenkins please :)

@cf-bottom

Copy link
Copy Markdown

@larsewi
larsewi merged commit b722369 into cfengine:master Oct 15, 2026
45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants