diff options
author | Trumeet <yuuta@yuuta.moe> | 2022-07-26 21:27:24 -0700 |
---|---|---|
committer | Trumeet <yuuta@yuuta.moe> | 2022-07-26 21:27:24 -0700 |
commit | c6948fd983fa1855b2dadba50aa50f576f54bdda (patch) | |
tree | c673a57580e8a87f4ce5dc6acd9763393e667ee0 /client/acronc/handler_socket.c | |
parent | f635018a3309b1cdddd6546b50f0a18214899218 (diff) | |
download | acron-c6948fd983fa1855b2dadba50aa50f576f54bdda.tar acron-c6948fd983fa1855b2dadba50aa50f576f54bdda.tar.gz acron-c6948fd983fa1855b2dadba50aa50f576f54bdda.tar.bz2 acron-c6948fd983fa1855b2dadba50aa50f576f54bdda.zip |
fix(acronc): input <cmd><EOF> will cause illegal memory access
Cause:
1. stdin got cmd -> request
2. stdin got EOF -> ac_disconnect -> ac_conn becomes NULL
3. socket got response -> ac_receive(ac_conn) -> crash
Conclusion: ordering issue between ac_disconnect and socket read.
Solution:
Best way: pause the input until command returns
Cons:
1. Lost the advantage of background execution of commands (has to wait until done)
2. It is unreliable to determine if a command is done: although the current server implementation will not send anything else after an error or cmd_result, Minecraft server itself or future server implementations may. The spec does not say anything on termination.
Acronc currently assumes that after receiving an error or cmd_result with the same request ID, it is done. Then, it resumes the stdin, reads the EOF, and then disconnect.
Worse way: Directly call uv_read_stop before ac_disconnect
Cons: It is going to lose anything, including command results. This is particularly undesirable for ad-hoc calls (i.e. echo list | acronc).
Therefore, the current approach is to block and read as much as we can (until error or cmd_result), then stop reading the socket before disconnecting as a double safe.
Signed-off-by: Trumeet <yuuta@yuuta.moe>
Diffstat (limited to 'client/acronc/handler_socket.c')
-rw-r--r-- | client/acronc/handler_socket.c | 17 |
1 files changed, 17 insertions, 0 deletions
diff --git a/client/acronc/handler_socket.c b/client/acronc/handler_socket.c index ff8eefb..efed127 100644 --- a/client/acronc/handler_socket.c +++ b/client/acronc/handler_socket.c @@ -42,6 +42,20 @@ static void ex2(bool force, bool cb) { force ? "true" : "false", RUNNING ? "true" : "false"); if (ac_conn) { + /* We do not want to read anything after. + * We will try to read as much as we can until an error or cmd result is received. + * Although it is possible that the server sends anything else with the same ID after, + * we will discard it. + * (The above applies to ad-hoc executions of acronc, e.g. echo cmd | acronc, where + * an EOF immediately follows the command.) + * If we do not wait, on_read will come AFTER ac_disconnect, thus calling ac_receive + * on a dangling pointer. + * If we simply stop reading the socket upon ac_disconnect, everything will be lost, including + * the command outputs, which is undesirable for ad-hoc runs. + * Therefore, the approach is to pause stdin until we believe that it is unlikely for any new + * responses to come, then resume stdin, and exit. */ + LOGD("Stopping socket input."); + uv_read_stop((uv_stream_t *) &sock); ac_disconnect(ac_conn, RUNNING ? force : true /* If sock is not running, always force */); ac_conn = NULL; @@ -106,6 +120,9 @@ static void on_read(uv_stream_t *tcp, ssize_t nread, const uv_buf_t *buf) { ex(true); /* Force close libac connection here. */ return; } + if (!ac_conn) { + LOGE("on_read called after ac_disconnect!"); + } int r; ac_obj_t *obj = NULL; if ((r = ac_receive(ac_conn, buf->base, nread, &obj))) { |