mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-12-06 19:41:15 +01:00
When parsing a multiline controller command, be careful with linebreaks
The first line break in particular was mishandled: it was discarded if no arguments came before it, which made it impossible to distinguish arguments from the first line of the body. To solve this, we need to allocate a copy of the command rather than using NUL to separate it, since we might have "COMMAND\n" as our input. Fixes ticket 29984.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
o Minor bugfixes (controller protocol):
|
||||
- Teach the controller parser to correctly distinguish an object
|
||||
preceded by an argument list from one without. Previously, it
|
||||
couldn't distinguish an argument list from the first line of a
|
||||
multiline object. Fixes bug 29984; bugfix on 0.2.3.8-alpha.
|
||||
@@ -56,7 +56,7 @@ problem function-size /src/app/main/ntmain.c:nt_service_install() 125
|
||||
problem include-count /src/app/main/shutdown.c 52
|
||||
problem file-size /src/core/mainloop/connection.c 5558
|
||||
problem include-count /src/core/mainloop/connection.c 61
|
||||
problem function-size /src/core/mainloop/connection.c:connection_free_minimal() 184
|
||||
problem function-size /src/core/mainloop/connection.c:connection_free_minimal() 185
|
||||
problem function-size /src/core/mainloop/connection.c:connection_listener_new() 328
|
||||
problem function-size /src/core/mainloop/connection.c:connection_handle_listener_read() 161
|
||||
problem function-size /src/core/mainloop/connection.c:connection_connect_sockaddr() 103
|
||||
|
||||
@@ -697,6 +697,7 @@ connection_free_minimal(connection_t *conn)
|
||||
control_connection_t *control_conn = TO_CONTROL_CONN(conn);
|
||||
tor_free(control_conn->safecookie_client_hash);
|
||||
tor_free(control_conn->incoming_cmd);
|
||||
tor_free(control_conn->current_cmd);
|
||||
if (control_conn->ephemeral_onion_services) {
|
||||
SMARTLIST_FOREACH(control_conn->ephemeral_onion_services, char *, cp, {
|
||||
memwipe(cp, 0, strlen(cp));
|
||||
|
||||
@@ -276,25 +276,38 @@ peek_connection_has_http_command(connection_t *conn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: take a nul-terminated command of given length, and find where
|
||||
* the command starts and the argument begins. Separate them with a NUL,
|
||||
* and return a pointer to the arguments.
|
||||
* Helper: take a nul-terminated command of given length, and find where the
|
||||
* command starts and the arguments begin. Separate them, allocate a new
|
||||
* string in <b>current_cmd_out</b> for the command, and return a pointer
|
||||
* to the arguments.
|
||||
**/
|
||||
STATIC char *
|
||||
control_split_incoming_command(char *incoming_cmd, size_t *data_len)
|
||||
control_split_incoming_command(char *incoming_cmd,
|
||||
size_t *data_len,
|
||||
char **current_cmd_out)
|
||||
{
|
||||
const bool is_multiline = *data_len && incoming_cmd[0] == '+';
|
||||
size_t cmd_len = 0;
|
||||
while (cmd_len < *data_len
|
||||
&& !TOR_ISSPACE(incoming_cmd[cmd_len]))
|
||||
++cmd_len;
|
||||
|
||||
incoming_cmd[cmd_len]='\0';
|
||||
char *args = incoming_cmd+cmd_len+1;
|
||||
tor_assert(*data_len>cmd_len);
|
||||
*data_len -= (cmd_len+1); /* skip the command and NUL we added after it */
|
||||
while (TOR_ISSPACE(*args)) {
|
||||
++args;
|
||||
--*data_len;
|
||||
*current_cmd_out = tor_memdup_nulterm(incoming_cmd, cmd_len);
|
||||
char *args = incoming_cmd+cmd_len;
|
||||
tor_assert(*data_len>=cmd_len);
|
||||
*data_len -= cmd_len;
|
||||
if (is_multiline) {
|
||||
// Only match horizontal space: any line after the first is data,
|
||||
// not arguments.
|
||||
while ((*args == '\t' || *args == ' ') && *data_len) {
|
||||
++args;
|
||||
--*data_len;
|
||||
}
|
||||
} else {
|
||||
while (TOR_ISSPACE(*args) && *data_len) {
|
||||
++args;
|
||||
--*data_len;
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
@@ -429,7 +442,11 @@ connection_control_process_inbuf(control_connection_t *conn)
|
||||
/* Okay, we now have a command sitting on conn->incoming_cmd. See if we
|
||||
* recognize it.
|
||||
*/
|
||||
args = control_split_incoming_command(conn->incoming_cmd, &data_len);
|
||||
tor_free(conn->current_cmd);
|
||||
args = control_split_incoming_command(conn->incoming_cmd, &data_len,
|
||||
&conn->current_cmd);
|
||||
if (BUG(!conn->current_cmd))
|
||||
return -1;
|
||||
|
||||
/* If the connection is already closing, ignore further commands */
|
||||
if (TO_CONN(conn)->marked_for_close) {
|
||||
@@ -437,14 +454,14 @@ connection_control_process_inbuf(control_connection_t *conn)
|
||||
}
|
||||
|
||||
/* Otherwise, Quit is always valid. */
|
||||
if (!strcasecmp(conn->incoming_cmd, "QUIT")) {
|
||||
if (!strcasecmp(conn->current_cmd, "QUIT")) {
|
||||
connection_write_str_to_buf("250 closing connection\r\n", conn);
|
||||
connection_mark_and_flush(TO_CONN(conn));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH &&
|
||||
!is_valid_initial_command(conn, conn->incoming_cmd)) {
|
||||
!is_valid_initial_command(conn, conn->current_cmd)) {
|
||||
connection_write_str_to_buf("514 Authentication required.\r\n", conn);
|
||||
connection_mark_for_close(TO_CONN(conn));
|
||||
return 0;
|
||||
|
||||
@@ -62,7 +62,8 @@ void set_cached_network_liveness(int liveness);
|
||||
|
||||
#ifdef CONTROL_PRIVATE
|
||||
STATIC char *control_split_incoming_command(char *incoming_cmd,
|
||||
size_t *data_len);
|
||||
size_t *data_len,
|
||||
char **current_cmd_out);
|
||||
#endif
|
||||
|
||||
#endif /* !defined(TOR_CONTROL_H) */
|
||||
|
||||
@@ -2299,7 +2299,7 @@ handle_control_obsolete(control_connection_t *conn,
|
||||
{
|
||||
(void)arg_len;
|
||||
(void)args;
|
||||
char *command = tor_strdup(conn->incoming_cmd);
|
||||
char *command = tor_strdup(conn->current_cmd);
|
||||
tor_strupper(command);
|
||||
connection_printf_to_buf(conn, "511 %s is obsolete.\r\n", command);
|
||||
tor_free(command);
|
||||
@@ -2490,14 +2490,14 @@ handle_single_control_command(const control_cmd_def_t *def,
|
||||
control_cmd_args_t *parsed_args;
|
||||
char *err=NULL;
|
||||
tor_assert(def->syntax);
|
||||
parsed_args = control_cmd_parse_args(conn->incoming_cmd,
|
||||
parsed_args = control_cmd_parse_args(conn->current_cmd,
|
||||
def->syntax,
|
||||
cmd_data_len, args,
|
||||
&err);
|
||||
if (!parsed_args) {
|
||||
connection_printf_to_buf(conn,
|
||||
"512 Bad arguments to %s: %s\r\n",
|
||||
conn->incoming_cmd, err?err:"");
|
||||
conn->current_cmd, err?err:"");
|
||||
tor_free(err);
|
||||
} else {
|
||||
if (BUG(err))
|
||||
@@ -2519,7 +2519,7 @@ handle_single_control_command(const control_cmd_def_t *def,
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a given controller command, as selected by the incoming_cmd field of
|
||||
* Run a given controller command, as selected by the current_cmd field of
|
||||
* <b>conn</b>.
|
||||
*/
|
||||
int
|
||||
@@ -2533,13 +2533,13 @@ handle_control_command(control_connection_t *conn,
|
||||
|
||||
for (unsigned i = 0; i < N_CONTROL_COMMANDS; ++i) {
|
||||
const control_cmd_def_t *def = &CONTROL_COMMANDS[i];
|
||||
if (!strcasecmp(conn->incoming_cmd, def->name)) {
|
||||
if (!strcasecmp(conn->current_cmd, def->name)) {
|
||||
return handle_single_control_command(def, conn, cmd_data_len, args);
|
||||
}
|
||||
}
|
||||
|
||||
connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n",
|
||||
conn->incoming_cmd);
|
||||
conn->current_cmd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ struct control_connection_t {
|
||||
/** A control command that we're reading from the inbuf, but which has not
|
||||
* yet arrived completely. */
|
||||
char *incoming_cmd;
|
||||
/** The control command that we are currently processing. */
|
||||
char *current_cmd;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user