Project

General

Profile

Bug #8880

Updated by Jeff Lucovsky 14 days ago

FTP tracks two globals with similar names: ftp_config_memcap, the configured 
 limit that FTPCheckMemcap enforces, and ftp_memcap, a counter of how often 
 that limit was hit. Three separate problems follow from allocations and 
 commands consulting the wrong one of these, or neither. 

 The unix socket commands operate on the hit counter, not the limit. 
 src/runmode-unix-socket.c registers FTPSetMemcap and FTPMemcapGlobalCounter 
 as the set/get pair for "ftp", and both of those touch the hit counter. So 
 "memcap-set ftp <value>" reports success while changing nothing that is 
 enforced -- there is no way to adjust the FTP memcap at runtime -- and as a 
 side effect it overwrites the ftp.memcap statistic with the requested byte 
 size. "memcap-show ftp" and "memcap-list" have the matching problem in 
 reverse, printing the hit counter formatted as a size: 

   $ suricata --unix-socket --set app-layer.protocols.ftp.memcap=10mb 
   $ suricatasc -c "memcap-show ftp" 
   {"message": {"value": "unlimited"}, "return": "OK"} 

 A process that has not yet hit its memcap reports the FTP limit as 
 "unlimited" no matter what the configuration says. Setting 0 for unlimited 
 is also rejected, unlike the other memcaps. 

 ftp.memuse leaks on command lines with trailing whitespace. 
 CopyCommandLine allocates line->len + 1 bytes, then trims trailing 
 whitespace by decrementing line->len, and returns the post-trim length. 
 That value is stored as tx->request_length and is what FTPTransactionFree 
 credits back, so the difference stays in ftp.memuse permanently. The 
 whitespace has to be inside the line rather than the delimiter -- 
 FTPGetLineForDirection already strips CR/LF -- so this needs commands such 
 as "USER user     \r\n". A five-command session with 11 trailing whitespace 
 characters ends with ftp.memuse at 11 after the flow is gone. Since memuse 
 only drifts upwards, the effective memcap tightens over the life of the 
 process. 

 Two allocations are charged to ftp.memuse without consulting the memcap. 
 The transfer command allocated by SCFTPTransferCmdNew, and the response 
 line payload whose total_size is added to memuse when the wrapper is built, 
 are both added to ftp.memuse with no memcap check. Running a session whose 
 server response is ~3000 bytes under app-layer.protocols.ftp.memcap=2048 
 stores the response and pushes memuse past the configured limit; the next 
 allocation that does check is then denied. The excess is bounded, but the 
 memcap is not a ceiling. 

 ftp.command_data reads past a null pointer once a request copy is denied. 
 DetectFTPCommandDataGetData decides whether a transaction carries command 
 data with (tx->request_length - b_len - 1) > 0. request_length is a uint32_t 
 and b_len a uint8_t, so the subtraction is unsigned: a transaction whose 
 request allocation was denied has request_length 0, the expression evaluates 
 to 0xfffffffb, and the buffer reaches the matcher as tx->request + b_len + 1, 
 an offset from a NULL pointer, with a length of 4294967291. FTPParseRequest 
 fills in tx->command_descriptor before calling CopyCommandLine, so the denied 
 copy leaves a valid command code behind and the transaction is inspected like 
 any other. With a rule using ftp.command_data loaded, a session that pipelines 
 commands until ftp.memuse reaches a 1024 byte memcap ends the process on 
 SIGSEGV. 

 Expected: memcap-set/memcap-show operate on the enforced limit and accept 0 
 for unlimited; ftp.memuse returns to 0 once transactions are freed; no 
 allocation charged to ftp.memuse bypasses the memcap check. 

 Affected versions: 8.0.x and 9.0.0-dev have all three. 
 

Back