Summary
In this report, we describe multiple vulnerabilities we discovered in Rsync.
The first pair of vulnerabilities are a Heap Buffer Overflow and an Info Leak. When combined, they allow a client to execute arbitrary code on the machine a Rsync server is running on. The client only requires anonymous read-access to the server.
We developed a reliable Proof-of-Concept exploit for the following Binary release:
Distro: Debian 12
Rsync version: 3.2.7
Binary MD5: 003765c378f66a4ac14a4f7ee43f7132
Invocation:Rsync --daemon
The two vulnerabilities reside in code that is reachable in any configuration and should thus be exploitable in any binary that includes the vulnerable code.
Additionally, we disclose 3 further vulnerabilities:
A Path Traversal issue in the client that allows a malicious server to exfiltrate the contents of any file on the client’s machine
A bypass for the --safe-links
CLI flag that can allow a malicious server to place unsafe symbolic links in a clients directory
A Path Traversal issue in the client that allows a malicious server to overwrite arbitrary files on the client's machine when either the -l
and the -l
isn't formatted ( but the subsequent -a
and --archive
are).
Severity
High - Multiple vulnerabilities impacting Rsync ranging from moderate (6.5) to Critical (9.8) CVSS scores which could allow a client to execute arbitrary code.
Proof of Concept
CVE-2024-12084 - Heap Buffer Overflow in Checksum Parsing
When the checksums are read by the daemon, two different checksums read:
- A 32-bit Adler-CRC32 Checksum
- A digest of the file chunk. The digest algorithm is determined at the beginning of the protocol negotiation.
The corresponding code can be seen below:
sender.c
s->sums = new_array(struct sum_buf, s->count);
for (i = 0; i < s->count; i++) {
s->sums[i].sum1 = read_int(f);
read_buf(f, s->sums[i].sum2, s->s2length);
Most importantly, note that sum2
field is filled with s->s2length bytes
. sum2
always has a size of 16:
Rsync.h
#define SUM_LENGTH 16
// . . .
struct sum_buf {
OFF_T offset; /**< offset in file of this chunk */
int32 len; /**< length of chunk of file */
uint32 sum1; /**< simple checksum */
int32 chain; /**< next hash-table collision */
short flags; /**< flag bits */
char sum2[SUM_LENGTH]; /**< checksum */
};
s2length
is an attacker-controlled value and can have a value up to MAX_DIGEST_LEN
bytes, as the next snipper shows:
io.c
sum->s2length = protocol_version < 27 ? csum_length : (int)read_int(f);
if (sum->s2length < 0 || sum->s2length > MAX_DIGEST_LEN) {
rprintf(FERROR, "Invalid checksum length %d [%s]\n",
sum->s2length, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
The problem here is that MAX_DIGEST_LEN
can be larger than 16 bytes, depending on the digest support the binary was compiled with:
md-defines.h
#define MD4_DIGEST_LEN 16
#define MD5_DIGEST_LEN 16
#if defined SHA512_DIGEST_LENGTH
#define MAX_DIGEST_LEN SHA512_DIGEST_LENGTH
#elif defined SHA256_DIGEST_LENGTH
#define MAX_DIGEST_LEN SHA256_DIGEST_LENGTH
#elif defined SHA_DIGEST_LENGTH
#define MAX_DIGEST_LEN SHA_DIGEST_LENGTH
#else
#define MAX_DIGEST_LEN MD5_DIGEST_LEN
#endif
SHA256
support is common and sets the MAX_DIGEST_LENGTH
value to 64. As a result, an attacker can write up to 48 bytes past the sum2
buffer limit.
This was the case for the following Ubuntu and Debian latest releases:
The Heap Buffer overflow was introduced with commit ae16850.
CVE-2024-12085 - Info Leak via uninitialized Stack contents defeats ASLR
The daemon matches checksums of chunks the client sent to the server against the local file contents in hash_search()
. Part of the function prologue is to allocate a buffer on the stack of MAX_DIGEST_LEN
bytes:
match.c
static void hash_search(int f,struct sum_struct *s,
struct map_struct *buf, OFF_T len)
{
OFF_T offset, aligned_offset, end;
int32 k, want_i, aligned_i, backup;
char sum2[MAX_DIGEST_LEN];
The daemon then iterates over the checksums the client sent and generates a digest for each of the chunks and compares them to the remote digest:
if (!done_csum2) {
map = (schar *)map_ptr(buf,offset,l);
get_checksum2((char *)map,l,sum2);
done_csum2 = 1;
}
if (memcmp(sum2,s->sums[i].sum2,s->s2length) != 0) {
false_alarms++;
continue;
}
Notably, the number of bytes that are compared again are s2length
bytes. In this case, the comparison does not go out of bounds since s2length
can be a maximum of MAX_DIGEST_LEN
.
However, the local sum2
buffer, not to be confused with the attacker-controlled s->sums[i].sum2
, is a buffer on the stack that is not cleared and thus contains uninitialized stack contents.
A malicious client can send a (known) xxhash64
checksum for a given chunk of a file, which leads to the daemon writing 8 bytes to the stack buffer sum2
. The attacker can then set s2length
to 9 bytes. The result of such a setup would be that the first 8 bytes match and an attacker-controlled 9th byte is compared with an unknown value of uninitialized stack data.
An attacker can divide a file into 255 chunks and as a result leak one byte per file download. An attacker can incrementally repeat the process, either in the same connection or by resetting the connection.
As a result, they can leak MAX_DIGEST_LEN
- 8 bytes of uninitialized stack data, which can contain pointers to Heap objects, Stack cookies, local variables and pointers to global variables and return pointers. With those pointers they can defeat ASLR.
CVE-2024-12086 - Server leaks arbitrary client files
When a client connects to a malicious server the server is able to leak the contents of an arbitrary file on the client’s machine.
In read_ndx_and_attrs the client will read fnamecmp
type as well as the xname
from the server if the server sets the appropriate flags. The flag sanitize_paths
will not be set for the client.
if (iflags & ITEM_BASIS_TYPE_FOLLOWS)
fnamecmp_type = read_byte(f_in);
*type_ptr = fnamecmp_type;
if (iflags & ITEM_XNAME_FOLLOWS) {
if ((len = read_vstring(f_in, buf, MAXPATHLEN)) < 0)
exit_cleanup(RERR_PROTOCOL);
if (sanitize_paths) {
sanitize_path(buf, buf, "", 0, SP_DEFAULT);
len = strlen(buf);
}
} else {
*buf = '\0';
len = -1;
}
*len_ptr = len;
The caller (recv_files) then uses the server provided values to determine a file to compare the incoming data with.
case FNAMECMP_FUZZY:
if (file->dirname) {
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, file->dirname, xname);
fnamecmp = fnamecmpbuf;
} else
fnamecmp = xname;
break;
…
fd1 = do_open(fnamecmp, O_RDONLY, 0);
In receive_data the contents of the file specified by xname
are copied into the destination file. This can be achieved by the server sending a negative token.
while ((i = recv_token(f_in, &data)) != 0) {
..snip..
if (i > 0) {
..snip..
}
..snip..
if (fd != -1 && map && write_file(fd, 0, offset, map, len) != (int)len)
The server sends a checksum to compare. If they don't match, a 0 is returned.
if (fd != -1 && memcmp(file_sum1, sender_file_sum, xfer_sum_len) != 0)
return 0;
When the return value is 0 the receiver will then send a MSG_REDO
to the generator. The generator will then write a message to the server.
The server can use this as a signal to determine if the checksum they sent was correct. By starting off with a blength
of 1 a malicious server is able to determine the contents of the target file byte by byte.
CVE-2024-12087 - Server can make client write files outside of destination directory using symbolic links
When the syncing of symbolic links is enabled, either through the -l
or -a
(--archive
) flags, a malicious server can make the client write arbitrary files outside of the destination directory.
A malicious server can send the client a file list such as:
symlink ->/arbitrary/directory
symlink/poc.txt
Symbolic links, by default, can be absolute or contain characters such as ../../.
In practice, the client validates the file list and when it sees the symlink/poc.txt
entry, it will look for a directory called symlink
, otherwise it will error out. If the server sends symlink as a directory and a symlink
, it will only keep the directory entry, thus the attack requires some more details to work.
In inc_recurse
mode, which the server can enable for the client, the server sends the client multiple file lists. The deduplication of the entries happens on a per-file-list basis. As a result, a malicious server can send a client multiple file lists, where:
# file list 1:
.
./symlink (directory)
./symlink/poc.txt (regular file)
# file list 2:
./symlink -> /arbitrary/path (symlink)
As a result, the symlink
directory is created first and symlink/poc.txt
is considered a valid entry in the file list. Then, the attacker changes the type of symlink
to a symbolic link.
When the server then instructs the client to create the symlink/poc.txt
file, it will follow the symbolic link and thus files can be created outside of the destination directory.
The --safe-links
CLI flag makes the client validate any symbolic links it receives from the server. The desired behavior is that symbolic links target can only be 1) relative to the destination directory and 2) never point outside of the destination directory.
The unsafe_symlink()
function is responsible for validating these symbolic links. The function calculates the traversal depth of a symbolic link target, relative to its position within the destination directory.
As an example, the following symbolic link is considered unsafe:
{DESTINATION}/foo -> ../../
As it points outside the destination directory. On the other hand, the following symbolic link is considered safe as it still points within the destination directory:
{DESTINATION}/foo -> a/b/c/d/e/f/../../
This function can be bypassed as it does not consider if the destination of a symbolic link contains other symbolic links in the path. For example, take the following two symbolic links:
{DESTINATION}/a -> .
{DESTINATION}/foo -> a/a/a/a/a/a/../../
In this case, foo
would actually point outside the destination directory. However, the unsafe_symlink
function assumes that a/
is a directory and that the symbolic link is safe.
Further Analysis
Rsync patches for these vulnerabilities can be found here: https://download.samba.org/pub/rsync/NEWS#3.4.0.
Timeline
Date reported: 10/29/2024
Date fixed: 01/14/2024
Date disclosed: 02/19/2025
Summary
In this report, we describe multiple vulnerabilities we discovered in Rsync.
The first pair of vulnerabilities are a Heap Buffer Overflow and an Info Leak. When combined, they allow a client to execute arbitrary code on the machine a Rsync server is running on. The client only requires anonymous read-access to the server.
We developed a reliable Proof-of-Concept exploit for the following Binary release:
Distro: Debian 12
Rsync version: 3.2.7
Binary MD5: 003765c378f66a4ac14a4f7ee43f7132
Invocation:
Rsync --daemon
The two vulnerabilities reside in code that is reachable in any configuration and should thus be exploitable in any binary that includes the vulnerable code.
Additionally, we disclose 3 further vulnerabilities:
A Path Traversal issue in the client that allows a malicious server to exfiltrate the contents of any file on the client’s machine
A bypass for the
--safe-links
CLI flag that can allow a malicious server to place unsafe symbolic links in a clients directoryA Path Traversal issue in the client that allows a malicious server to overwrite arbitrary files on the client's machine when either the
-l
and the-l
isn't formatted ( but the subsequent-a
and--archive
are).Severity
High - Multiple vulnerabilities impacting Rsync ranging from moderate (6.5) to Critical (9.8) CVSS scores which could allow a client to execute arbitrary code.
Proof of Concept
CVE-2024-12084 - Heap Buffer Overflow in Checksum Parsing
When the checksums are read by the daemon, two different checksums read:
The corresponding code can be seen below:
sender.c
Most importantly, note that
sum2
field is filled withs->s2length bytes
.sum2
always has a size of 16:Rsync.h
s2length
is an attacker-controlled value and can have a value up toMAX_DIGEST_LEN
bytes, as the next snipper shows:io.c
The problem here is that
MAX_DIGEST_LEN
can be larger than 16 bytes, depending on the digest support the binary was compiled with:md-defines.h
SHA256
support is common and sets theMAX_DIGEST_LENGTH
value to 64. As a result, an attacker can write up to 48 bytes past thesum2
buffer limit.This was the case for the following Ubuntu and Debian latest releases:
The Heap Buffer overflow was introduced with commit ae16850.
CVE-2024-12085 - Info Leak via uninitialized Stack contents defeats ASLR
The daemon matches checksums of chunks the client sent to the server against the local file contents in
hash_search()
. Part of the function prologue is to allocate a buffer on the stack ofMAX_DIGEST_LEN
bytes:match.c
The daemon then iterates over the checksums the client sent and generates a digest for each of the chunks and compares them to the remote digest:
Notably, the number of bytes that are compared again are
s2length
bytes. In this case, the comparison does not go out of bounds sinces2length
can be a maximum ofMAX_DIGEST_LEN
.However, the local
sum2
buffer, not to be confused with the attacker-controlleds->sums[i].sum2
, is a buffer on the stack that is not cleared and thus contains uninitialized stack contents.A malicious client can send a (known)
xxhash64
checksum for a given chunk of a file, which leads to the daemon writing 8 bytes to the stack buffersum2
. The attacker can then sets2length
to 9 bytes. The result of such a setup would be that the first 8 bytes match and an attacker-controlled 9th byte is compared with an unknown value of uninitialized stack data.An attacker can divide a file into 255 chunks and as a result leak one byte per file download. An attacker can incrementally repeat the process, either in the same connection or by resetting the connection.
As a result, they can leak
MAX_DIGEST_LEN
- 8 bytes of uninitialized stack data, which can contain pointers to Heap objects, Stack cookies, local variables and pointers to global variables and return pointers. With those pointers they can defeat ASLR.CVE-2024-12086 - Server leaks arbitrary client files
When a client connects to a malicious server the server is able to leak the contents of an arbitrary file on the client’s machine.
In read_ndx_and_attrs the client will read
fnamecmp
type as well as thexname
from the server if the server sets the appropriate flags. The flagsanitize_paths
will not be set for the client.The caller (recv_files) then uses the server provided values to determine a file to compare the incoming data with.
In receive_data the contents of the file specified by
xname
are copied into the destination file. This can be achieved by the server sending a negative token.The server sends a checksum to compare. If they don't match, a 0 is returned.
When the return value is 0 the receiver will then send a
MSG_REDO
to the generator. The generator will then write a message to the server.The server can use this as a signal to determine if the checksum they sent was correct. By starting off with a
blength
of 1 a malicious server is able to determine the contents of the target file byte by byte.CVE-2024-12087 - Server can make client write files outside of destination directory using symbolic links
When the syncing of symbolic links is enabled, either through the
-l
or-a
(--archive
) flags, a malicious server can make the client write arbitrary files outside of the destination directory.A malicious server can send the client a file list such as:
Symbolic links, by default, can be absolute or contain characters such as
../../.
In practice, the client validates the file list and when it sees the
symlink/poc.txt
entry, it will look for a directory calledsymlink
, otherwise it will error out. If the server sends symlink as a directory and asymlink
, it will only keep the directory entry, thus the attack requires some more details to work.In
inc_recurse
mode, which the server can enable for the client, the server sends the client multiple file lists. The deduplication of the entries happens on a per-file-list basis. As a result, a malicious server can send a client multiple file lists, where:As a result, the
symlink
directory is created first andsymlink/poc.txt
is considered a valid entry in the file list. Then, the attacker changes the type ofsymlink
to a symbolic link.When the server then instructs the client to create the
symlink/poc.txt
file, it will follow the symbolic link and thus files can be created outside of the destination directory.CVE-2024-12088 --safe-links Bypass
The
--safe-links
CLI flag makes the client validate any symbolic links it receives from the server. The desired behavior is that symbolic links target can only be 1) relative to the destination directory and 2) never point outside of the destination directory.The
unsafe_symlink()
function is responsible for validating these symbolic links. The function calculates the traversal depth of a symbolic link target, relative to its position within the destination directory.As an example, the following symbolic link is considered unsafe:
{DESTINATION}/foo -> ../../
As it points outside the destination directory. On the other hand, the following symbolic link is considered safe as it still points within the destination directory:
{DESTINATION}/foo -> a/b/c/d/e/f/../../
This function can be bypassed as it does not consider if the destination of a symbolic link contains other symbolic links in the path. For example, take the following two symbolic links:
{DESTINATION}/a -> .
{DESTINATION}/foo -> a/a/a/a/a/a/../../
In this case,
foo
would actually point outside the destination directory. However, theunsafe_symlink
function assumes thata/
is a directory and that the symbolic link is safe.Further Analysis
Rsync patches for these vulnerabilities can be found here: https://download.samba.org/pub/rsync/NEWS#3.4.0.
Timeline
Date reported: 10/29/2024
Date fixed: 01/14/2024
Date disclosed: 02/19/2025