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.