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. Two Three separate problems follow from allocations and 
 commands 
 consulting the wrong one of these, or neither. Two more follow from what the 
 parser and the detect layer do once the memcap denies an allocation, or once a 
 response line is rejected. 

 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: 

 <pre> 
 

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

 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. 

 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. 

 The response truncation state survives a line the parser rejects. 
 FTPParseResponse clears state->current_line_truncated_tc only after a response 
 wrapper has been allocated and linked onto the transaction. A line that 
 SCFTPParseResponseLine rejects -- it returns NULL when the line trims to 
 nothing -- or whose wrapper allocation the memcap denies, leaves the flag set. 
 FTPGetLineForDirection then takes the next line for the tail of the truncated 
 one: it discards that line, clears the flag and returns APP_LAYER_ERROR. The 
 reply that followed is lost and the replies after it are logged one 
 transaction late. With app-layer.protocols.ftp.max-line-length=64 and a 
 100 byte whitespace reply to USER, the 230 answering PASS never appears and 
 the 221 answering QUIT is logged against PASS: 

 <pre> 
 {"command": "PASS", "command_data": "password", "completion_code": ["221"], "reply": ["Goodbye."], "reply_received": "yes"} 
 {"command": "QUIT", "reply_received": "no"} 
 </pre> 

 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; 
 a transaction whose request was denied presents an empty ftp.command_data 
 buffer; a rejected response line leaves the truncation state clear, so the 
 reply that follows is parsed and logged against its own transaction. check. 

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

Back