-
Notifications
You must be signed in to change notification settings - Fork 322
CORE-629: Fix use-after-free in BRPeerManager/BRPeer #295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -99,7 +99,6 @@ typedef struct { | |
uint32_t magicNumber; | ||
char host[INET6_ADDRSTRLEN]; | ||
BRPeerStatus status; | ||
int waitingForNetwork; | ||
volatile int needsFilterUpdate; | ||
uint64_t nonce, feePerKb; | ||
char *useragent; | ||
|
@@ -985,7 +984,7 @@ static double _peerGetMempoolTime (BRPeerContext *ctx) { | |
} | ||
|
||
|
||
static void *_peerThreadRoutine(void *arg) | ||
static void *_peerThreadConnectRoutine(void *arg) | ||
{ | ||
BRPeer *peer = arg; | ||
BRPeerContext *ctx = arg; | ||
|
@@ -1114,6 +1113,22 @@ static void *_peerThreadRoutine(void *arg) | |
return NULL; // detached threads don't need to return a value | ||
} | ||
|
||
static void *_peerThreadDisconnectRoutine(void *arg) { | ||
BRPeer *peer = arg; | ||
BRPeerContext *ctx = arg; | ||
|
||
pthread_cleanup_push(ctx->threadCleanup, ctx->info); | ||
|
||
peer_log(peer, "waiting-disconnected"); | ||
|
||
assert (0 == array_count(ctx->pongCallback)); | ||
assert (NULL == ctx->mempoolCallback); | ||
|
||
if (ctx->disconnected) ctx->disconnected(ctx->info, 0); | ||
pthread_cleanup_pop(1); | ||
return NULL; // detached threads don't need to return a value | ||
} | ||
|
||
static void _dummyThreadCleanup(void *info) | ||
{ | ||
} | ||
|
@@ -1226,35 +1241,31 @@ void BRPeerConnect(BRPeer *peer) | |
pthread_attr_t attr; | ||
|
||
pthread_mutex_lock(&ctx->lock); | ||
if (ctx->status == BRPeerStatusDisconnected || ctx->waitingForNetwork) { | ||
ctx->status = BRPeerStatusConnecting; | ||
|
||
if (ctx->status == BRPeerStatusDisconnected || ctx->status == BRPeerStatusWaiting) { | ||
if (ctx->networkIsReachable && ! ctx->networkIsReachable(ctx->info)) { // delay until network is reachable | ||
if (! ctx->waitingForNetwork) peer_log(peer, "waiting for network reachability"); | ||
ctx->waitingForNetwork = 1; | ||
if (ctx->status != BRPeerStatusWaiting) peer_log(peer, "waiting for network reachability"); | ||
ctx->status = BRPeerStatusWaiting; | ||
} | ||
else { | ||
peer_log(peer, "connecting"); | ||
ctx->waitingForNetwork = 0; | ||
ctx->status = BRPeerStatusConnecting; | ||
gettimeofday(&tv, NULL); | ||
|
||
// No race - set before the thread starts. | ||
ctx->disconnectTime = tv.tv_sec + (double)tv.tv_usec/1000000 + CONNECT_TIMEOUT; | ||
|
||
if (pthread_attr_init(&attr) != 0) { | ||
// error = ENOMEM; | ||
peer_log(peer, "error creating thread"); | ||
peer_log(peer, "error creating connect thread"); | ||
ctx->status = BRPeerStatusDisconnected; | ||
//if (ctx->disconnected) ctx->disconnected(ctx->info, error); | ||
assert (0); | ||
} | ||
else if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0 || | ||
pthread_attr_setstacksize(&attr, PTHREAD_STACK_SIZE) != 0 || | ||
pthread_create(&ctx->thread, &attr, _peerThreadRoutine, peer) != 0) { | ||
// error = EAGAIN; | ||
peer_log(peer, "error creating thread"); | ||
pthread_create(&ctx->thread, &attr, _peerThreadConnectRoutine, peer) != 0) { | ||
peer_log(peer, "error creating connect thread"); | ||
pthread_attr_destroy(&attr); | ||
ctx->status = BRPeerStatusDisconnected; | ||
//if (ctx->disconnected) ctx->disconnected(ctx->info, error); | ||
assert (0); | ||
} | ||
} | ||
} | ||
|
@@ -1266,15 +1277,36 @@ void BRPeerDisconnect(BRPeer *peer) | |
{ | ||
BRPeerContext *ctx = (BRPeerContext *)peer; | ||
int socket = -1; | ||
pthread_attr_t attr; | ||
|
||
if (_peerCheckAndGetSocket(ctx, &socket)) { | ||
pthread_mutex_lock(&ctx->lock); | ||
ctx->status = BRPeerStatusDisconnected; | ||
pthread_mutex_unlock(&ctx->lock); | ||
|
||
if (shutdown(socket, SHUT_RDWR) < 0) peer_log(peer, "%s", strerror(errno)); | ||
close(socket); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likely the most controversial part; the spawning of a "cleanup"-like thread. Also, the dual nature of this function... |
||
pthread_mutex_lock(&ctx->lock); | ||
if (ctx->status == BRPeerStatusWaiting) { | ||
peer_log(peer, "disconnecting"); | ||
ctx->status = BRPeerStatusDisconnected; | ||
|
||
if (pthread_attr_init(&attr) != 0) { | ||
peer_log(peer, "error creating disconnect thread"); | ||
assert (0); | ||
} | ||
else if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0 || | ||
pthread_attr_setstacksize(&attr, PTHREAD_STACK_SIZE) != 0 || | ||
pthread_create(&ctx->thread, &attr, _peerThreadDisconnectRoutine, peer) != 0) { | ||
peer_log(peer, "error creating disconnect thread"); | ||
pthread_attr_destroy(&attr); | ||
assert (0); | ||
} | ||
|
||
ctx->status = BRPeerStatusDisconnected; | ||
} | ||
pthread_mutex_unlock(&ctx->lock); | ||
} | ||
|
||
// call this to (re)schedule a disconnect in the given number of seconds, or < 0 to cancel (useful for sync timeout) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,7 +93,8 @@ extern "C" { | |
typedef enum { | ||
BRPeerStatusDisconnected = 0, | ||
BRPeerStatusConnecting, | ||
BRPeerStatusConnected | ||
BRPeerStatusConnected, | ||
BRPeerStatusWaiting | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Roll the separate |
||
} BRPeerStatus; | ||
|
||
typedef struct { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -839,7 +839,7 @@ static void _peerDisconnected(void *info, int error) | |
if (manager->connectFailureCount > MAX_CONNECT_FAILURES) manager->connectFailureCount = MAX_CONNECT_FAILURES; | ||
} | ||
|
||
if (! manager->isConnected && manager->connectFailureCount == MAX_CONNECT_FAILURES) { | ||
if (! manager->isConnected && manager->connectFailureCount >= MAX_CONNECT_FAILURES) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd seen cases where this was greater than MAX_CONNECT_FAILURES. In |
||
_BRPeerManagerSyncStopped(manager); | ||
|
||
// clear out stored peers so we get a fresh list from DNS on next connect attempt | ||
|
@@ -1498,6 +1498,7 @@ static void _peerThreadCleanup(void *info) | |
|
||
free(info); | ||
pthread_mutex_lock(&manager->lock); | ||
assert (0 != manager->peerThreadCount); | ||
manager->peerThreadCount--; | ||
pthread_mutex_unlock(&manager->lock); | ||
if (manager->threadCleanup) manager->threadCleanup(manager->info); | ||
|
@@ -1664,7 +1665,7 @@ void BRPeerManagerConnect(BRPeerManager *manager) | |
for (size_t i = array_count(manager->connectedPeers); i > 0; i--) { | ||
BRPeer *p = manager->connectedPeers[i - 1]; | ||
|
||
if (BRPeerConnectStatus(p) == BRPeerStatusConnecting) BRPeerConnect(p); | ||
if (BRPeerConnectStatus(p) == BRPeerStatusWaiting) BRPeerConnect(p); | ||
} | ||
|
||
if (array_count(manager->connectedPeers) < manager->maxConnectCount) { | ||
|
@@ -1706,13 +1707,6 @@ void BRPeerManagerConnect(BRPeerManager *manager) | |
_peerSetFeePerKb, _peerRequestedTx, _peerNetworkIsReachable, _peerThreadCleanup); | ||
BRPeerSetEarliestKeyTime(info->peer, manager->earliestKeyTime); | ||
BRPeerConnect(info->peer); | ||
|
||
if (BRPeerConnectStatus(info->peer) == BRPeerStatusDisconnected) { | ||
pthread_mutex_unlock(&manager->lock); | ||
_peerDisconnected(info, ENOTCONN); | ||
pthread_mutex_lock(&manager->lock); | ||
manager->peerThreadCount--; | ||
} | ||
} | ||
} | ||
|
||
|
@@ -1744,7 +1738,7 @@ void BRPeerManagerDisconnect(BRPeerManager *manager) | |
p = manager->connectedPeers[i - 1]; | ||
manager->connectFailureCount = MAX_CONNECT_FAILURES; // prevent futher automatic reconnect attempts | ||
BRPeerDisconnect(p); | ||
if (BRPeerConnectStatus(p) == BRPeerStatusConnecting) manager->peerThreadCount--; // waiting for network | ||
while (BRPeerConnectStatus(p) == BRPeerStatusConnecting) BRPeerDisconnect(p); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes me leery (endless loop anyone?) but the logic should be that this only happens when the thread is being spawned. Once spawned, the BRPeer thread will set the status to connected or disconnected. In either case, this will break out (fingers crossed but also seen in testing). |
||
} | ||
|
||
peerThreadCount = manager->peerThreadCount; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These probably aren't needed. Added them due to my unfamiliarity with this aspect of the BTC code (assert would catch if we needed to handle this).