apache / celix

Apache Celix is a framework for C and C++14 to develop dynamic modular software applications using component and in-process service-oriented programming.
https://celix.apache.org/
Apache License 2.0
162 stars 86 forks source link

Use-after-free caused by cancelled tracker #595

Closed PengZheng closed 1 year ago

PengZheng commented 1 year ago

When eliminating GOTOs in discovery_zeroconf, we found that replacing the following celix_bundleContext_stopTrackerAsync plus celix_bundleContext_stopTrackerAsync by celix_bundleContext_stop will caused ASAN error in TEST_F(DiscoveryZeroconfWatcherTestSuite, CreateWatcherFailed4).

celix_status_t discoveryZeroconfWatcher_create(celix_bundle_context_t *ctx, celix_log_helper_t *logHelper, discovery_zeroconf_watcher_t **watcherOut) {
// omitted
thread_err:
    celix_bundleContext_stopTrackerAsync(ctx, watcher->epListenerTrkId, NULL, NULL);
    celix_bundleContext_waitForAsyncStopTracker(ctx, watcher->epListenerTrkId);
epl_tracker_err:
    celix_longHashMap_destroy(watcher->epls);
    celix_stringHashMap_destroy(watcher->watchedServices);
    celix_stringHashMap_destroy(watcher->watchedEndpoints);
    celixThreadMutex_destroy(&watcher->mutex);
mutex_err:
fw_uuid_err:
    close(watcher->eventFd);
event_fd_err:
    free(watcher);
watcher_err:
    return status;
}

The difference between the two procedures is that the latter may cancel a tracker while the former will never try to cancel a in-flight tracker. A closer look at service tracker creation reveals that a cancelled tracker will be created and destroyed even if it is cancelled, which in our case will lead to use-after-free.

static void celix_bundleContext_createTrackerOnEventLoop(void *data) {
    celix_bundle_context_service_tracker_entry_t* entry = data;
    assert(celix_framework_isCurrentThreadTheEventLoop(entry->ctx->framework));
    celixThreadMutex_lock(&entry->ctx->mutex);
    bool cancelled = entry->cancelled;
    celixThreadMutex_unlock(&entry->ctx->mutex);
    if (cancelled) {
        fw_log(entry->ctx->framework->logger, CELIX_LOG_LEVEL_DEBUG, "Creating of service tracker was cancelled. trk id = %li, svc name tracked = %s", entry->trackerId, entry->opts.filter.serviceName);
    } else {
        celix_service_tracker_t *tracker = celix_serviceTracker_createWithOptions(entry->ctx, &entry->opts);
        if (tracker != NULL) {
            celixThreadMutex_lock(&entry->ctx->mutex);
            // double-check is necessary to eliminate first-check-then-do race condition
            if(!entry->cancelled) {
                entry->tracker = tracker;
            }
            celixThreadMutex_unlock(&entry->ctx->mutex);
            if(entry->tracker == NULL) {
                assert(entry->cancelled);
                celix_serviceTracker_destroy(tracker);
            }
        } else {
            fw_log(entry->ctx->framework->logger, CELIX_LOG_LEVEL_ERROR, "Cannot create tracker for bnd %s (%li)", celix_bundle_getSymbolicName(entry->ctx->bundle), celix_bundle_getId(entry->ctx->bundle));
        }
    }
}