Repository navigation
Conversation
sline_release_hold() unlinks and frees the entry it is given. The hold-queue timeout callback then read entry->token from the freed memory to build its debug messages. Copy the token before releasing. Only debug builds compile the offending reads, and they are reads, so the visible effect was garbage in a Debug() line rather than a crash.
ntohs() yields a 16-bit value that is promoted to int before the shift, so (ntohs(x) << 16) overflows for any IPv4 address whose first two octets are 192.0 or higher (0xC000 << 16 does not fit in a signed int). That is undefined behaviour, even though every compiler in use happens to produce the intended bit pattern. Shift as unsigned.
msg_tag_parse() does not copy: each tag's key and value point into the sending client's connection buffer. parse.c kept the list in a static that was only reset when the next line was parsed, and send_buffer() falls back to that list whenever it is called without an explicit tag context. So any send made outside a command handler -- exit_client() from a socket EOF/error callback, a timer -- was tagged with whatever the last parsed line carried. That is wrong on its own (an unrelated client's ERROR gets someone else's label/msgid), and once the client that sent that line has been freed it is a use-after-free of its connection. Clear the list in packet.c right after each dispatch, so the fallback is only live while the handler that owns the line is running.
client_timer_callback() drains pending input with read_packet(), which can exit the client (a QUIT in the buffer, a registration failure) and free its struct Client. The trailing consistency assert then read cli_connect() from the freed structure. Return early when read_packet() reports CPTR_KILLED.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four small memory-safety fixes, one per commit. Two are stale reads of a
freed structure, one is a use-after-free of a connection buffer that is
also a logic bug, and one is undefined behaviour in address encoding.
sline.c— stale read after the timeout callback releases an entrysline_hold_timeout_callback()callssline_release_hold(entry->token),which unlinks and frees the entry, then reads
entry->tokenagain forthe two
Debug()lines that follow. Copy the token before the call.Debug builds only; reads only.
numnicks.c— signed shift overflow iniptobase64()ntohs()yields a 16-bit value that is promoted toint, so(ntohs(x) << 16)overflows for any IPv4 address whose first two octetsare 192.0 or higher. Every compiler in use produces the intended bit
pattern, but it is undefined behaviour. Shift as unsigned. Present since
the 2004 IPv6 work.
parse.c/packet.c— message tags outlive the line they came frommsg_tag_parse()does not copy: each tag's key and value point into thesending client's
con_buffer.parse.ckeeps the list in a static thatis only reset when the next line is parsed, and
send_buffer()fallsback to that list whenever it is called without an explicit tag context.
So any send made outside a command handler picked up the previous line's
tags:
exit_client()from a socket EOF/error callback, anything drivenby a timer. That is wrong even when nothing has been freed (an unrelated
client's
ERROR :Closing Linkcarries someone else'slabel/msgid),and once the client that sent that line is gone it is a read from a
freed connection.
Clear the list in
packet.cright after each dispatch, so the fallbackis only live while the handler that owns the line runs. Every send made
from a handler is unaffected.
s_bsd.c— process timer touches a clientread_packet()freedclient_timer_callback()drains pending input withread_packet(),which can exit the client (a QUIT in the buffer, a registration failure)
and free its
struct Client. The trailing consistency assert then readcli_connect()from the freed structure. Return whenread_packet()reports
CPTR_KILLED.Testing
Builds warning-free (
--enable-warnings);ircd/testunit suite passes;the docker integration suite passes with the ircds built with
-fsanitize=address,undefined.