Fix type mismatch in new/delete of addrinfo::ai_addr#1671
Fix type mismatch in new/delete of addrinfo::ai_addr#1671jtstrs wants to merge 15 commits intosquid-cache:masterfrom
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
code polish
This comment was marked as outdated.
This comment was marked as outdated.
|
Please also double check the adjusted PR title/description. @jtstrs, have you seen this bug result in actual memory leaks with some common memory management library? I am just curious. It is a bug regardless of whether those memory leaks are common in some typical environments... |
|
@rousskov There typical log from ASan for this case: |
It looks to me like you found a genuine issue. |
I would like to second Francesco's opinion: @jtstrs, your research/PR is very valuable, even if there are usually no memory leaks associated with the new/delete mismatch bugs you have uncovered. @jtstrs, would you like to refactor PR changes to isolate ai_addr construction (including correct length setting and zeroing) and destruction into dedicated functions or should we take a stab at that first? Either way is fine; just let us know. |
|
I'll prefer to stick an approach with encapsulation of construction/deleting |
This comment was marked as outdated.
This comment was marked as outdated.
…f github.com:jtstrs/squid into ip/address_bugfix_FixMemoryLeakAfterReleasingAddress
| memset(ai->ai_addr, 0, sizeof(struct sockaddr_in6)); | ||
|
|
||
| ai->ai_addrlen = sizeof(struct sockaddr_in6); | ||
| FreeAddrMember(*ai); |
There was a problem hiding this comment.
We can also move this call into a new else clause for the above addrinfo allocation if statement.
| debugs(14, DBG_CRITICAL, "ERROR: Squid Bug: Unexpected addrinfo::ai_addr size: " << ai.ai_addrlen << | ||
| Debug::Extra << "sockaddr_in size: " << sizeof(struct sockaddr_in) << | ||
| Debug::Extra << "sockaddr_in6 size: " << sizeof(struct sockaddr_in6)); | ||
| // leak memory |
There was a problem hiding this comment.
Or we can assert() instead. Throwing in cleanup code is not a good idea.
There was a problem hiding this comment.
assert() would also be a bad idea for now. There are struct sockaddr_un stored for UDS sockets Comm::Connection. They have variable-length allocations.
There was a problem hiding this comment.
There are
struct sockaddr_unstored for UDS socketsComm::Connection. They have variable-length allocations.
N.B. comm_open_uds() does not use this function (i.e. does not call Ip::Address::InitAddr() and FreeAddr()).
If you know of any Squid code that (directly or indirectly) allocates addrinfo::ai_addr using something other than the two PR-handled types and then calls (directly or indirectly) Ip::Address::FreeAddr(), please point me to it. I could not find it. I hope such code does not exist, but if it does exist, then this PR will need to be changed to reflect its existence. (This PR code will need to be changed for other reasons as well, of course, but I am documenting one of the assumptions that the current PR design relies on and that is not directly related to those other reasons).
There is code that allocates addrinfo::ai_addr using getaddrinfo(), but that code calls freeaddrinfo(), as it should. That code does not directly affect this part of the discussion.
|
Changes looks good from my side. |
|
I'll check it patch with ASan again and add comment with results little bit later |
src/ip/Address.cc
Outdated
There was a problem hiding this comment.
Squid also stores struct sockaddr_storage when the exact address type is unknown before being filled with values.
(Please double-check my suggestion. I am not sure if the ai_addrlen value actually survives the syscall()'s which fill in the address value.)
| } else { | |
| else if (ai.ai_addrlen == sizeof(struct sockaddr_storage)) | |
| delete reinterpret_cast<struct sockaddr_storage*>(ai.ai_addr); | |
| else { |
There was a problem hiding this comment.
I am not sure if the ai_addrlen value actually survives the syscall()'s which fill in the address value.
Good catch! I agree that the PR code is buggy: We cannot always rely on ai_addrlen to be preserved during lifetime of an Ip::Address::InitAddr()-allocated addrinfo object:
recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address,
socklen_t *restrict address_len);
... The address_len argument is a value-result argument, initialized to the size of
the buffer associated with address, and modified on return to indicate the actual size of the address
stored there.
Same for getsockname() and probably other system calls.
struct addrinfo *AI = nullptr;
Ip::Address::InitAddr(AI);
int x = recvfrom(fd, buf, len, flags, AI->ai_addr, &AI->ai_addrlen);
...
Ip::Address::FreeAddr(AI);Same for at least one getsockname() caller -- comm_local_port().
To move closer to a solution, we should stop (ab)using addrinfo type for affected recvfrom(), getsockname(), and possibly other system calls that have nothing to do with addrinfo. Most likely, all those calls should not use dynamic memory allocation at all! If we are lucky, eliminating Ip::Address::InitAddr() callers that do not need addrinfo will reduce the original problem footprint enough for the existing PR Init()/Free() implementation to start working reliably.
There was a problem hiding this comment.
FTR; these were an attempt to standardize on one C-type for the IP::Address API. It did not get completed and the individual sockaddr_* type methods removed. struct addrinfo was chosen as it is the formal type used by modern IPv6-enabled APIs. It was begun before I learned of sockaddr_storage which is essentially the C equivalent of what Ip::Address is supposed to be.
In hindsight the syscalls using any sockaddr_* type should have been converted to sockaddr_storage. So we have addrinfo for just the DNS related APIs and sockaddr_storage for the socket APIs. Dropping support of the other methods from Ip::Address
There was a problem hiding this comment.
@jtstrs, I believe Amos' recollection supports my recommendation and gives you an additional hint with regard to the right type to use for at least some of the system calls you are considering converting away from addrinfo.
| debugs(14, DBG_CRITICAL, "ERROR: Squid Bug: Unexpected addrinfo::ai_addr size: " << ai.ai_addrlen << | ||
| Debug::Extra << "sockaddr_in size: " << sizeof(struct sockaddr_in) << | ||
| Debug::Extra << "sockaddr_in6 size: " << sizeof(struct sockaddr_in6)); | ||
| // leak memory |
There was a problem hiding this comment.
assert() would also be a bad idea for now. There are struct sockaddr_un stored for UDS sockets Comm::Connection. They have variable-length allocations.
src/ip/Address.cc
Outdated
There was a problem hiding this comment.
I am not sure if the ai_addrlen value actually survives the syscall()'s which fill in the address value.
Good catch! I agree that the PR code is buggy: We cannot always rely on ai_addrlen to be preserved during lifetime of an Ip::Address::InitAddr()-allocated addrinfo object:
recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address,
socklen_t *restrict address_len);
... The address_len argument is a value-result argument, initialized to the size of
the buffer associated with address, and modified on return to indicate the actual size of the address
stored there.
Same for getsockname() and probably other system calls.
struct addrinfo *AI = nullptr;
Ip::Address::InitAddr(AI);
int x = recvfrom(fd, buf, len, flags, AI->ai_addr, &AI->ai_addrlen);
...
Ip::Address::FreeAddr(AI);Same for at least one getsockname() caller -- comm_local_port().
To move closer to a solution, we should stop (ab)using addrinfo type for affected recvfrom(), getsockname(), and possibly other system calls that have nothing to do with addrinfo. Most likely, all those calls should not use dynamic memory allocation at all! If we are lucky, eliminating Ip::Address::InitAddr() callers that do not need addrinfo will reduce the original problem footprint enough for the existing PR Init()/Free() implementation to start working reliably.
| debugs(14, DBG_CRITICAL, "ERROR: Squid Bug: Unexpected addrinfo::ai_addr size: " << ai.ai_addrlen << | ||
| Debug::Extra << "sockaddr_in size: " << sizeof(struct sockaddr_in) << | ||
| Debug::Extra << "sockaddr_in6 size: " << sizeof(struct sockaddr_in6)); | ||
| // leak memory |
There was a problem hiding this comment.
There are
struct sockaddr_unstored for UDS socketsComm::Connection. They have variable-length allocations.
N.B. comm_open_uds() does not use this function (i.e. does not call Ip::Address::InitAddr() and FreeAddr()).
If you know of any Squid code that (directly or indirectly) allocates addrinfo::ai_addr using something other than the two PR-handled types and then calls (directly or indirectly) Ip::Address::FreeAddr(), please point me to it. I could not find it. I hope such code does not exist, but if it does exist, then this PR will need to be changed to reflect its existence. (This PR code will need to be changed for other reasons as well, of course, but I am documenting one of the assumptions that the current PR design relies on and that is not directly related to those other reasons).
There is code that allocates addrinfo::ai_addr using getaddrinfo(), but that code calls freeaddrinfo(), as it should. That code does not directly affect this part of the discussion.
Personally, I recommend a slightly different approach: Find one addrinfo use case where addrinfo object is dynamically allocated, its length argument is supplied to some system call like recvfrom() to be updated, but the object as a whole is not used for anything (e.g. it is not then given to some other function to do something else). That is the simplest use case I can think of. Post a pull request dedicated to removing that one unnecessary addrinfo usage. Make sure the new code handles any reasonable (for the given context) address and errors. Once that PR lands, we will know how to address several similar (and possibly even all of the remaining) cases. If that plan works, eventually, we will get back to this PR with no dynamic addrinfo allocation cases left except for those this PR already handles correctly. Icmp4::Recv() is a candidate but there might be even simpler use cases (Icmp4::Recv() does feed that addrinfo object to one of the fifty1 Ip::Address assignment operators, but it is possible that you will find another Ip::Address method that accomplishes what we want without adding one). Footnotes
|
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
|
I've started analysis of things which we discussed above, and I'm afraid, that I could'nt found any "dummy" usages |
Icmp::Log() does not take addrinfo. It takes Ip::Address. Thus, Icmp::Log() is a red herring on the path to removing excessive addrinfo use. If you are studying Icmp4::Recv(), then decide what recvfrom() argument to use to store the "from" address and then how to convert that filled "from" address to Ip::Address (i.e. to One possible (but not guaranteed) way to find an acceptable solution for the above problem is the following process:
|
|
Got it, thanks for hints, will proceed analysis then |
|
Probably we could use |
Ip::Address does need a lot of changes, but a major development like changing Ip::Address storage type is way outside this PR scope (regardless of its long-term merits). Correctly creating a new module to "wrap all system API calls" is also a huge undertaking. With all due respect, this kind of development should not be driven by a new Squid developer -- correct changes on those paths require too many Squid-specific (and difficult) decisions. Please start small instead! Let's see how we can solve a few isolated/small problems first. Use Ip::Address as needed; add or adjust a method or two if you must, but stop if class changes snowball beyond that. Use sockaddr_storage as/if needed. Eliminate one or two specific addrinfo use cases. We will then decide how to generalize your fixes. This PR changes may affect Ip::Address, its future rewrites, and even creation of new modules, of course, but I hope that we do not need to cross that bridge right here, right now, before we have settled a single addrinfo removal change. |
|
Alternative solution taken up in PR #2136 |
deleteused addrinfo::ai_addr type (i.e. sockaddr)newcalls use sockaddr_in typenewcalls use sockaddr_in6 typeMismatching new/delete types result in undefined behavior. However,
since these are all PODs, the bug may not have resulted in runtime
problems in most environments, even though sockaddr_in6 is usually
larger than sockaddr and sockaddr_in tructure (e.g., 28 vs. 16 bytes).