-
Notifications
You must be signed in to change notification settings - Fork 95
Closed
Description
Summary
serviceTracker_getService (deprecated) returns the service pointer without retaining tracked->useCount or waiting for ongoing unregister. If another thread unregisters the same service, serviceTracker_untrackTracked waits for useCount to drop to 1, then calls bundleContext_ungetService and frees tracked. Any caller still using the old pointer hits freed memory → UAF.
Component
- File:
libs/framework/src/service_tracker.c - API:
serviceTracker_getService(deprecated but still callable) - Scenario: concurrent “get service” vs “unregister service”
Root Cause
serviceTracker_getServiceonly locks the list and returnstracked->service; it does not bump useCount or keep the entry alive.serviceTracker_untrackTrackedwaits for useCount, then ungets and frees the tracked entry; previously returned raw pointers become dangling.- The function is marked “TODO deprecated warning -> not locked” but is neither disabled nor concurrency-safe, so multithreaded callers (common in async callbacks) hit UAF.
Key code excerpts:
// returns raw pointer, no useCount retain
void *serviceTracker_getService(service_tracker_t* tracker) {
//TODO deprecated warning -> not locked
void *service = NULL;
celixThreadMutex_lock(&tracker->state.mutex);
... // find tracked->service
celixThreadMutex_unlock(&tracker->state.mutex);
return service;
}// unregister path eventually frees tracked
static void serviceTracker_untrackTracked(...) {
serviceTracker_invokeRemovingService(tracker, tracked);
...
celixThreadMutex_lock(&tracked->mutex);
while (tracked->useCount > 1) {
celixThreadCondition_wait(&tracked->useCond, &tracked->mutex);
}
celixThreadMutex_unlock(&tracked->mutex);
bundleContext_ungetService(tracker->context, tracked->reference, &ungetSuccess);
bundleContext_ungetServiceReference(tracker->context, tracked->reference);
assert(tracked->useCount == 1);
... // destroy mutex/cond & free(tracked)
}Reproduction (self-contained, suitable for Issue review)
- Minimal gtest (drop into any test target):
TEST(ServiceTrackerUafRepro, DeprecatedGetServiceUaf) {
struct dummy { uint64_t magic; };
celix_properties_t* props = celix_properties_create();
celix_properties_setBool(props, CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE, true);
celix_properties_set(props, CELIX_FRAMEWORK_CACHE_DIR, ".cacheServiceTrackerUaf");
celix_framework_t* fw = celix_frameworkFactory_createFramework(props);
auto* ctx = celix_framework_getFrameworkContext(fw);
auto* svc = static_cast<dummy*>(malloc(sizeof(dummy)));
svc->magic = 0xBEEFBEEF;
long svcId = celix_bundleContext_registerService(ctx, svc, "dummy_uaf", nullptr);
int handle = 0; // non-null handle required
service_tracker_customizer_pt cust = nullptr;
ASSERT_EQ(CELIX_SUCCESS, serviceTrackerCustomizer_create(&handle, nullptr, nullptr, nullptr, nullptr, &cust));
service_tracker_t* tracker = nullptr;
ASSERT_EQ(CELIX_SUCCESS, serviceTracker_create(ctx, "dummy_uaf", cust, &tracker));
ASSERT_EQ(CELIX_SUCCESS, serviceTracker_open(tracker));
std::atomic<bool> stop{false};
std::thread tGet([&] {
while (!stop.load()) {
auto* s = static_cast<dummy*>(serviceTracker_getService(tracker));
if (s) { s->magic = 0xCAFEBABECAFED00DULL; }
}
});
std::thread tUnreg([&] {
for (int i = 0; i < 2000 && !stop.load(); ++i) {
celix_bundleContext_unregisterService(ctx, svcId);
free(svc); // provider frees immediately
svc = static_cast<dummy*>(malloc(sizeof(dummy)));
svc->magic = 0xDEADBEEF + i;
svcId = celix_bundleContext_registerService(ctx, svc, "dummy_uaf", nullptr);
}
stop.store(true);
});
tUnreg.join();
stop.store(true);
tGet.join();
celix_bundleContext_unregisterService(ctx, svcId);
free(svc);
serviceTracker_close(tracker);
serviceTracker_destroy(tracker);
serviceTrackerCustomizer_destroy(cust);
celix_frameworkFactory_destroyFramework(fw);
// Expected: crash / ASan UAF because getService returns a freed object.
}-
Add the test file to
libs/framework/gtestand list it inCELIX_FRAMEWORK_TEST_SOURCESinlibs/framework/gtest/CMakeLists.txt. -
Build & run (Debug/ASan recommended):
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_TESTING=ON -DBUILD_TESTING=ON
cmake --build build --target test_framework -j4
cd build && ./libs/framework/gtest/test_frameworkd --gtest_filter=*ServiceTrackerUaf*
- Expected result: runtime crash like
free(): invalid size(exit 134) or ASan reports use-after-free; this showsserviceTracker_getServicereturns a dangling pointer when unregister races.
Impact
- Callers may get freed service pointers under concurrency, leading to crashes or wrong behavior.
- Misuse risk is high because the deprecated API is still callable and older code may rely on it.
Metadata
Metadata
Assignees
Labels
No labels