Skip to content

Commit 119b27b

Browse files
committed
Fix leak of session objects on session close
SessionObjects were kept alive in allObjects as tombstones with valid=false after their session closed, so every object ever created lingered until C_Finalize, holding its Mutex*. Long running clients saw unbounded memory growth dominated by mutex objects. Add refcounting to OSObject and release the store's reference in sessionClosed, allSessionsClosed, tokenLoggedOut, deleteObject, clearStore, and ~SessionObjectStore. Also erase from allObjects in these paths to avoid a double-free against the destructor.
1 parent 02cebd5 commit 119b27b

3 files changed

Lines changed: 144 additions & 126 deletions

File tree

src/lib/object_store/OSObject.h

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,27 @@
3838
#include "config.h"
3939
#include "OSAttribute.h"
4040
#include "cryptoki.h"
41+
#include <atomic>
4142

4243
class OSObject
4344
{
4445
public:
45-
// Destructor
46-
virtual ~OSObject() { }
46+
OSObject() : refCount(1) {}
47+
48+
virtual ~OSObject() = default;
49+
50+
void acquire() noexcept
51+
{
52+
refCount.fetch_add(1, std::memory_order_relaxed);
53+
}
54+
55+
void release() noexcept
56+
{
57+
if (refCount.fetch_sub(1, std::memory_order_acq_rel) == 1)
58+
{
59+
delete this;
60+
}
61+
}
4762

4863
// Check if the specified attribute exists
4964
virtual bool attributeExists(CK_ATTRIBUTE_TYPE type) = 0;
@@ -89,6 +104,8 @@ class OSObject
89104
// Destroys the object (warning, any pointers to the object are no longer
90105
// valid after this call because delete is called!)
91106
virtual bool destroyObject() = 0;
107+
private:
108+
std::atomic<int> refCount;
92109
};
93110

94111
#endif // !_SOFTHSM_V2_OSOBJECT_H

src/lib/object_store/SessionObjectStore.cpp

Lines changed: 78 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,10 @@ SessionObjectStore::~SessionObjectStore()
5959
std::set<SessionObject*> cleanUp = allObjects;
6060
allObjects.clear();
6161

62-
for (std::set<SessionObject*>::iterator i = cleanUp.begin(); i != cleanUp.end(); i++)
62+
for (auto* obj : cleanUp)
6363
{
64-
if ((*i) == NULL) continue;
65-
66-
SessionObject* that = *i;
67-
delete that;
64+
if (obj == NULL) continue;
65+
obj->release();
6866
}
6967

7068
MutexFactory::i()->recycleMutex(storeMutex);
@@ -129,90 +127,109 @@ SessionObject* SessionObjectStore::createObject(CK_SLOT_ID slotID, CK_SESSION_HA
129127
// Delete an object
130128
bool SessionObjectStore::deleteObject(SessionObject* object)
131129
{
132-
MutexLocker lock(storeMutex);
133-
134-
if (objects.find(object) == objects.end())
130+
SessionObject* toRelease = NULL;
135131
{
136-
ERROR_MSG("Cannot delete non-existent object 0x%08X", object);
137-
138-
return false;
132+
MutexLocker lock(storeMutex);
133+
if (objects.find(object) == objects.end())
134+
{
135+
ERROR_MSG("Cannot delete non-existent object 0x%08X", object);
136+
return false;
137+
}
138+
object->invalidate();
139+
objects.erase(object);
140+
allObjects.erase(object);
141+
toRelease = object;
142+
}
143+
if (toRelease)
144+
{
145+
toRelease->release();
139146
}
140-
141-
// Invalidate the object instance
142-
object->invalidate();
143-
144-
objects.erase(object);
145-
146147
return true;
147148
}
148149

149-
// Indicate that a session has been closed; invalidates all objects
150-
// associated with this session
150+
// Indicate that a session has been closed - remove all its objects
151151
void SessionObjectStore::sessionClosed(CK_SESSION_HANDLE hSession)
152152
{
153-
MutexLocker lock(storeMutex);
154-
155-
std::set<SessionObject*> checkObjects = objects;
156-
157-
for (std::set<SessionObject*>::iterator i = checkObjects.begin(); i != checkObjects.end(); i++)
153+
std::set<SessionObject*> toRelease;
158154
{
159-
if ((*i)->removeOnSessionClose(hSession))
160-
{
161-
// Since the object remains in the allObjects set, any pointers to it will
162-
// remain valid but it will no longer be returned when the set of objects
163-
// is requested
164-
objects.erase(*i);
155+
MutexLocker lock(storeMutex);
156+
for (auto it = objects.begin(); it != objects.end(); ) {
157+
if ((*it)->removeOnSessionClose(hSession))
158+
{
159+
toRelease.insert(*it);
160+
allObjects.erase(*it);
161+
it = objects.erase(it);
162+
}
163+
else
164+
{
165+
++it;
166+
}
165167
}
166-
}
168+
}
169+
for (auto* obj : toRelease)
170+
{
171+
obj->release();
172+
}
167173
}
168174

169175
void SessionObjectStore::allSessionsClosed(CK_SLOT_ID slotID)
170176
{
171-
MutexLocker lock(storeMutex);
172-
173-
std::set<SessionObject*> checkObjects = objects;
174-
175-
for (std::set<SessionObject*>::iterator i = checkObjects.begin(); i != checkObjects.end(); i++)
176-
{
177-
if ((*i)->removeOnAllSessionsClose(slotID))
177+
std::set<SessionObject*> toRelease;
178178
{
179-
// Since the object remains in the allObjects set, any pointers to it will
180-
// remain valid but it will no longer be returned when the set of objects
181-
// is requested
182-
objects.erase(*i);
179+
MutexLocker lock(storeMutex);
180+
for (auto it = objects.begin(); it != objects.end(); ) {
181+
if ((*it)->removeOnAllSessionsClose(slotID))
182+
{
183+
toRelease.insert(*it);
184+
allObjects.erase(*it);
185+
it = objects.erase(it);
186+
}
187+
else
188+
{
189+
++it;
190+
}
191+
}
183192
}
193+
for (auto* obj : toRelease)
194+
{
195+
obj->release();
184196
}
185197
}
186198

187199
void SessionObjectStore::tokenLoggedOut(CK_SLOT_ID slotID)
188200
{
189-
MutexLocker lock(storeMutex);
190-
191-
std::set<SessionObject*> checkObjects = objects;
192-
193-
for (std::set<SessionObject*>::iterator i = checkObjects.begin(); i != checkObjects.end(); i++)
201+
std::set<SessionObject*> toRelease;
194202
{
195-
if ((*i)->removeOnTokenLogout(slotID))
196-
{
197-
// Since the object remains in the allObjects set, any pointers to it will
198-
// remain valid but it will no longer be returned when the set of objects
199-
// is requested
200-
objects.erase(*i);
203+
MutexLocker lock(storeMutex);
204+
for (auto it = objects.begin(); it != objects.end(); ) {
205+
if ((*it)->removeOnTokenLogout(slotID)) {
206+
toRelease.insert(*it);
207+
allObjects.erase(*it);
208+
it = objects.erase(it);
209+
}
210+
else
211+
{
212+
++it;
213+
}
201214
}
202215
}
216+
for (auto* obj : toRelease)
217+
{
218+
obj->release();
219+
}
203220
}
204221

205222
// Clear the whole store
206223
void SessionObjectStore::clearStore()
207224
{
208-
MutexLocker lock(storeMutex);
209-
210-
objects.clear();
211-
std::set<SessionObject*> clearObjects = allObjects;
212-
allObjects.clear();
213-
214-
for (std::set<SessionObject*>::iterator i = clearObjects.begin(); i != clearObjects.end(); i++)
225+
std::set<SessionObject*> clearObjects;
226+
{
227+
MutexLocker lock(storeMutex);
228+
objects.clear();
229+
clearObjects.swap(allObjects);
230+
}
231+
for (auto* obj : clearObjects)
215232
{
216-
delete *i;
233+
obj->release();
217234
}
218235
}

src/lib/object_store/test/SessionObjectStoreTests.cpp

Lines changed: 47 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -181,115 +181,99 @@ void SessionObjectStoreTests::testMultiSession()
181181
// Check that the store contains 3 objects
182182
CPPUNIT_ASSERT(store->getObjectCount() == 3);
183183

184-
// Check that all three objects are distinct and present
184+
// Check that all objects are distinct and present
185185
std::set<OSObject*> objects;
186-
store->getObjects(objects);
187-
bool present1[3] = { false, false, false };
188-
189-
for (std::set<OSObject*>::iterator i = objects.begin(); i != objects.end(); i++)
186+
store->getObjects(objects);
187+
bool present[5] = { false, false, false, false, false };
188+
for (std::set<OSObject*>::iterator i = objects.begin(); i != objects.end(); ++i)
190189
{
191-
ByteString retrievedId;
192-
193190
CPPUNIT_ASSERT((*i)->isValid());
194191
CPPUNIT_ASSERT((*i)->attributeExists(CKA_ID));
195-
196192
CPPUNIT_ASSERT((*i)->getAttribute(CKA_ID).isByteStringAttribute());
197-
198-
for (int j = 0; j < 3; j++)
193+
for (int j = 0; j < 5; ++j)
199194
{
200195
if ((*i)->getAttribute(CKA_ID).getByteStringValue() == id[j])
201196
{
202-
present1[j] = true;
197+
present[j] = true;
203198
}
204199
}
205200
}
201+
CPPUNIT_ASSERT(present[0] && present[1] && present[2]);
202+
CPPUNIT_ASSERT(!present[3] && !present[4]);
206203

207-
for (int j = 0; j < 3; j++)
208-
{
209-
CPPUNIT_ASSERT(present1[j] == true);
210-
}
211-
212-
// Now indicate that the second session has been closed
204+
// Close session 2 -> obj2 is destroyed. Its pointer is now stale.
213205
store->sessionClosed(2);
206+
obj2 = NULL; // hygiene: prevent accidental use-after-free in the test
214207

215-
// Verify that it was indeed removed
216208
CPPUNIT_ASSERT(store->getObjectCount() == 2);
217209

218-
objects.clear();
210+
objects.clear();
219211
store->getObjects(objects);
220-
bool present3[2] = { false, false };
221-
222-
for (std::set<OSObject*>::iterator i = objects.begin(); i != objects.end(); i++)
212+
memset(present, 0, sizeof(present));
213+
for (std::set<OSObject*>::iterator i = objects.begin(); i != objects.end(); ++i)
223214
{
224-
ByteString retrievedId;
225-
226-
CPPUNIT_ASSERT((*i)->isValid());
227-
CPPUNIT_ASSERT((*i)->attributeExists(CKA_ID));
228-
229-
CPPUNIT_ASSERT((*i)->getAttribute(CKA_ID).isByteStringAttribute());
230-
231-
if ((*i)->getAttribute(CKA_ID).getByteStringValue() == id[0])
215+
for (int j = 0; j < 5; ++j)
232216
{
233-
present3[0] = true;
234-
}
235-
if ((*i)->getAttribute(CKA_ID).getByteStringValue() == id[2])
236-
{
237-
present3[1] = true;
217+
if ((*i)->getAttribute(CKA_ID).getByteStringValue() == id[j])
218+
{
219+
present[j] = true;
220+
}
238221
}
239222
}
223+
CPPUNIT_ASSERT(present[0] && !present[1] && present[2]);
240224

241-
for (int j = 0; j < 2; j++)
242-
{
243-
CPPUNIT_ASSERT(present3[j] == true);
244-
}
245-
246-
// Create two more objects for session 7
225+
// Create two more in session 7, with distinct IDs so we can identify them by attribute
247226
SessionObject* obj4 = store->createObject(1, 7);
248227
CPPUNIT_ASSERT(obj4 != NULL);
228+
obj4->setAttribute(CKA_ID, idAtt[3]);
249229
SessionObject* obj5 = store->createObject(1, 7);
250230
CPPUNIT_ASSERT(obj5 != NULL);
231+
obj5->setAttribute(CKA_ID, idAtt[4]);
251232

252233
CPPUNIT_ASSERT(store->getObjectCount() == 4);
253234

254-
// Close session 1
235+
// Close session 1 -> obj1 destroyed.
255236
store->sessionClosed(1);
237+
obj1 = NULL;
256238

257239
CPPUNIT_ASSERT(store->getObjectCount() == 3);
258240

259-
objects.clear();
241+
objects.clear();
260242
store->getObjects(objects);
243+
memset(present, 0, sizeof(present));
244+
for (std::set<OSObject*>::iterator i = objects.begin(); i != objects.end(); ++i)
245+
{
246+
for (int j = 0; j < 5; ++j)
247+
{
248+
if ((*i)->getAttribute(CKA_ID).getByteStringValue() == id[j])
249+
{
250+
present[j] = true;
251+
}
252+
}
253+
}
254+
CPPUNIT_ASSERT(!present[0] && !present[1]);
255+
CPPUNIT_ASSERT(present[2] && present[3] && present[4]);
261256

262-
CPPUNIT_ASSERT(objects.find(obj1) == objects.end());
263-
CPPUNIT_ASSERT(objects.find(obj2) == objects.end());
264-
CPPUNIT_ASSERT(objects.find(obj3) != objects.end());
265-
CPPUNIT_ASSERT(objects.find(obj4) != objects.end());
266-
CPPUNIT_ASSERT(objects.find(obj5) != objects.end());
267-
268-
CPPUNIT_ASSERT(!obj1->isValid());
269-
CPPUNIT_ASSERT(!obj2->isValid());
257+
// The objects whose sessions are still open should still be usable
270258
CPPUNIT_ASSERT(obj3->isValid());
271259
CPPUNIT_ASSERT(obj4->isValid());
272260
CPPUNIT_ASSERT(obj5->isValid());
273261

274-
// Close session 7
262+
// Close session 7 -> obj4, obj5 destroyed
275263
store->sessionClosed(7);
264+
obj4 = NULL;
265+
obj5 = NULL;
276266

277267
CPPUNIT_ASSERT(store->getObjectCount() == 1);
278268

279-
objects.clear();
269+
objects.clear();
280270
store->getObjects(objects);
271+
CPPUNIT_ASSERT(objects.size() == 1);
281272

282-
CPPUNIT_ASSERT(objects.find(obj1) == objects.end());
283-
CPPUNIT_ASSERT(objects.find(obj2) == objects.end());
284-
CPPUNIT_ASSERT(objects.find(obj3) != objects.end());
285-
CPPUNIT_ASSERT(objects.find(obj4) == objects.end());
286-
CPPUNIT_ASSERT(objects.find(obj5) == objects.end());
287-
288-
CPPUNIT_ASSERT(!obj1->isValid());
289-
CPPUNIT_ASSERT(!obj2->isValid());
273+
std::set<OSObject*>::iterator it = objects.begin();
274+
CPPUNIT_ASSERT((*it)->isValid());
275+
CPPUNIT_ASSERT((*it)->getAttribute(CKA_ID).getByteStringValue() == id[2]);
290276
CPPUNIT_ASSERT(obj3->isValid());
291-
CPPUNIT_ASSERT(!obj4->isValid());
292-
CPPUNIT_ASSERT(!obj5->isValid());
293277

294278
delete store;
295279
}

0 commit comments

Comments
 (0)