Line data Source code
1 : /* This Source Code Form is subject to the terms of the Mozilla Public
2 : * License, v. 2.0. If a copy of the MPL was not distributed with this
3 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 :
5 : #include "nsSmartCardMonitor.h"
6 :
7 : #include "ScopedNSSTypes.h"
8 : #include "mozilla/Services.h"
9 : #include "mozilla/Unused.h"
10 : #include "nsIObserverService.h"
11 : #include "nsServiceManagerUtils.h"
12 : #include "nsThreadUtils.h"
13 : #include "GeckoProfiler.h"
14 : #include "nspr.h"
15 : #include "pk11func.h"
16 :
17 : using namespace mozilla;
18 :
19 : //
20 : // The SmartCard monitoring thread should start up for each module we load
21 : // that has removable tokens. This code calls an NSS function which waits
22 : // until there is a change in the token state. NSS uses the
23 : // C_WaitForSlotEvent() call in PKCS #11 if the module implements the call,
24 : // otherwise NSS will poll the token in a loop with a delay of 'latency'
25 : // between polls. Note that the C_WaitForSlotEvent() may wake up on any type
26 : // of token event, so it's necessary to filter these events down to just the
27 : // insertion and removal events we are looking for.
28 : //
29 : // Once the event is found, it is dispatched to the main thread to notify
30 : // any window where window.crypto.enableSmartCardEvents is true.
31 : // Additionally, all observers of the topics |kSmartcardInsert| and
32 : // |kSmartcardRemove| are notified by the observer service of the appropriate
33 : // event.
34 : //
35 :
36 : #define kSmartcardInsert "smartcard-insert"
37 : #define kSmartcardRemove "smartcard-remove"
38 :
39 : class nsTokenEventRunnable : public nsIRunnable {
40 : public:
41 0 : nsTokenEventRunnable(const char* aType, const nsAString& aTokenName)
42 0 : : mType(aType)
43 0 : , mTokenName(aTokenName)
44 : {
45 0 : }
46 :
47 : NS_DECL_THREADSAFE_ISUPPORTS
48 : NS_DECL_NSIRUNNABLE
49 :
50 : private:
51 0 : virtual ~nsTokenEventRunnable() {}
52 :
53 : const char* mType;
54 : nsString mTokenName;
55 : };
56 :
57 0 : NS_IMPL_ISUPPORTS(nsTokenEventRunnable, nsIRunnable)
58 :
59 : NS_IMETHODIMP
60 0 : nsTokenEventRunnable::Run()
61 : {
62 0 : MOZ_ASSERT(NS_IsMainThread());
63 :
64 : nsCOMPtr<nsIObserverService> observerService =
65 0 : mozilla::services::GetObserverService();
66 0 : if (!observerService) {
67 0 : return NS_ERROR_FAILURE;
68 : }
69 0 : return observerService->NotifyObservers(nullptr, mType, mTokenName.get());
70 : }
71 :
72 : // self linking and removing double linked entry
73 : // adopts the thread it is passed.
74 : class SmartCardThreadEntry
75 : {
76 : public:
77 : friend class SmartCardThreadList;
78 0 : SmartCardThreadEntry(SmartCardMonitoringThread *thread,
79 : SmartCardThreadEntry *next,
80 : SmartCardThreadEntry *prev,
81 : SmartCardThreadEntry **head)
82 0 : : next(next)
83 : , prev(prev)
84 : , head(head)
85 0 : , thread(thread)
86 : {
87 0 : if (prev) {
88 0 : prev->next = this;
89 : } else {
90 0 : *head = this;
91 : }
92 0 : if (next) {
93 0 : next->prev = this;
94 : }
95 0 : }
96 :
97 0 : ~SmartCardThreadEntry()
98 0 : {
99 0 : if (prev) {
100 0 : prev->next = next;
101 : } else {
102 0 : *head = next;
103 : }
104 0 : if (next) {
105 0 : next->prev = prev;
106 : }
107 : // NOTE: automatically stops the thread
108 0 : delete thread;
109 0 : }
110 :
111 : private:
112 : SmartCardThreadEntry *next;
113 : SmartCardThreadEntry *prev;
114 : SmartCardThreadEntry **head;
115 : SmartCardMonitoringThread *thread;
116 : };
117 :
118 : //
119 : // SmartCardThreadList is a class to help manage the running threads.
120 : // That way new threads could be started and old ones terminated as we
121 : // load and unload modules.
122 : //
123 0 : SmartCardThreadList::SmartCardThreadList() : head(0)
124 : {
125 0 : }
126 :
127 0 : SmartCardThreadList::~SmartCardThreadList()
128 : {
129 : // the head is self linking and unlinking, the following
130 : // loop removes all entries on the list.
131 : // it will also stop the thread if it happens to be running
132 0 : while (head) {
133 0 : delete head;
134 : }
135 0 : }
136 :
137 : void
138 0 : SmartCardThreadList::Remove(SECMODModule *aModule)
139 : {
140 0 : for (SmartCardThreadEntry* current = head; current;
141 0 : current = current->next) {
142 0 : if (current->thread->GetModule() == aModule) {
143 : // NOTE: automatically stops the thread and dequeues it from the list
144 0 : delete current;
145 0 : return;
146 : }
147 : }
148 : }
149 :
150 : // adopts the thread passed to it. Starts the thread as well
151 : nsresult
152 0 : SmartCardThreadList::Add(SmartCardMonitoringThread* thread)
153 : {
154 : SmartCardThreadEntry* current = new SmartCardThreadEntry(thread, head,
155 0 : nullptr, &head);
156 : // OK to forget current here, it's on the list.
157 : Unused << current;
158 :
159 0 : return thread->Start();
160 : }
161 :
162 :
163 : // We really should have a Unity PL Hash function...
164 : static PLHashNumber
165 0 : unity(const void* key) { return PLHashNumber(NS_PTR_TO_INT32(key)); }
166 :
167 0 : SmartCardMonitoringThread::SmartCardMonitoringThread(SECMODModule* module_)
168 0 : : mThread(nullptr)
169 : {
170 0 : mModule = SECMOD_ReferenceModule(module_);
171 : // simple hash functions, most modules have less than 3 slots, so 10 buckets
172 : // should be plenty
173 0 : mHash = PL_NewHashTable(10, unity, PL_CompareValues, PL_CompareStrings,
174 : nullptr, 0);
175 0 : }
176 :
177 : //
178 : // when we shutdown the thread, be sure to stop it first. If not, it just might
179 : // crash when the mModule it is looking at disappears.
180 : //
181 0 : SmartCardMonitoringThread::~SmartCardMonitoringThread()
182 : {
183 0 : Stop();
184 0 : SECMOD_DestroyModule(mModule);
185 0 : if (mHash) {
186 0 : PL_HashTableDestroy(mHash);
187 : }
188 0 : }
189 :
190 : nsresult
191 0 : SmartCardMonitoringThread::Start()
192 : {
193 0 : if (!mThread) {
194 0 : mThread = PR_CreateThread(PR_SYSTEM_THREAD, LaunchExecute, this,
195 : PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
196 : PR_JOINABLE_THREAD, 0);
197 : }
198 0 : return mThread ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
199 : }
200 :
201 : //
202 : // Should only stop if we are through with the module.
203 : // CancelWait has the side effect of losing all the keys and
204 : // current operations on the module!. (See the comment in
205 : // SECMOD_CancelWait for why this is so..).
206 : //
207 0 : void SmartCardMonitoringThread::Stop()
208 : {
209 : SECStatus rv;
210 :
211 0 : rv = SECMOD_CancelWait(mModule);
212 0 : if (rv != SECSuccess) {
213 : // we didn't wake up the Wait, so don't try to join the thread
214 : // otherwise we will hang forever...
215 0 : return;
216 : }
217 :
218 : // confused about the memory model here? NSPR owns the memory for
219 : // threads. non-joinable threads are freed when the thread dies.
220 : // joinable threads are freed after the call to PR_JoinThread.
221 : // That means if SECMOD_CancelWait fails, we'll leak the mThread
222 : // structure. this is considered preferable to hanging (which is
223 : // what will happen if we try to join a thread that blocked).
224 0 : if (mThread) {
225 0 : PR_JoinThread(mThread);
226 0 : mThread = 0;
227 : }
228 : }
229 :
230 : //
231 : // remember the name and series of a token in a particular slot.
232 : // This is important because the name is no longer available when
233 : // the token is removed. If listeners depended on this information,
234 : // They would be out of luck. It also is a handy way of making sure
235 : // we don't generate spurious insertion and removal events as the slot
236 : // cycles through various states.
237 : //
238 : void
239 0 : SmartCardMonitoringThread::SetTokenName(CK_SLOT_ID slotid,
240 : const char* tokenName, uint32_t series)
241 : {
242 0 : if (mHash) {
243 0 : if (tokenName) {
244 0 : int len = strlen(tokenName) + 1;
245 : // Use PR_Malloc() because PLHashAllocOps.freeEntry for mHash is
246 : // DefaultFreeEntry(), which uses PR_Free().
247 0 : char* entry = (char*)PR_Malloc(len + sizeof(uint32_t));
248 :
249 0 : if (entry) {
250 0 : memcpy(entry, &series, sizeof(uint32_t));
251 0 : memcpy(&entry[sizeof(uint32_t)], tokenName, len);
252 :
253 0 : PL_HashTableAdd(mHash, (void*)(uintptr_t)slotid, entry); /* adopt */
254 0 : return;
255 : }
256 : } else {
257 : // if tokenName was not provided, remove the old one (implicit delete)
258 0 : PL_HashTableRemove(mHash, (void*)(uintptr_t)slotid);
259 : }
260 : }
261 : }
262 :
263 : // retrieve the name saved above
264 : const char*
265 0 : SmartCardMonitoringThread::GetTokenName(CK_SLOT_ID slotid)
266 : {
267 0 : const char* tokenName = nullptr;
268 : const char* entry;
269 :
270 0 : if (mHash) {
271 0 : entry = (const char*)PL_HashTableLookupConst(mHash,
272 0 : (void*)(uintptr_t)slotid);
273 0 : if (entry) {
274 0 : tokenName = &entry[sizeof(uint32_t)];
275 : }
276 : }
277 0 : return tokenName;
278 : }
279 :
280 : // retrieve the series saved in SetTokenName above
281 : uint32_t
282 0 : SmartCardMonitoringThread::GetTokenSeries(CK_SLOT_ID slotid)
283 : {
284 0 : uint32_t series = 0;
285 : const char* entry;
286 :
287 0 : if (mHash) {
288 0 : entry = (const char*)PL_HashTableLookupConst(mHash,
289 0 : (void*)(uintptr_t)slotid);
290 0 : if (entry) {
291 0 : memcpy(&series, entry, sizeof(uint32_t));
292 : }
293 : }
294 0 : return series;
295 : }
296 :
297 : //
298 : // helper function to pass the event off to nsNSSComponent.
299 : //
300 : void
301 0 : SmartCardMonitoringThread::SendEvent(const char* eventType,
302 : const char* tokenName)
303 : {
304 : // The token name should be UTF8, but it's not clear that this is enforced
305 : // by NSS. To be safe, we explicitly check here before converting it to
306 : // UTF16. If it isn't UTF8, we just use an empty string with the idea that
307 : // consumers of these events should at least be notified that something
308 : // happened.
309 0 : nsAutoString tokenNameUTF16(NS_LITERAL_STRING(""));
310 0 : if (IsUTF8(nsDependentCString(tokenName))) {
311 0 : tokenNameUTF16.Assign(NS_ConvertUTF8toUTF16(tokenName));
312 : }
313 : nsCOMPtr<nsIRunnable> runnable(new nsTokenEventRunnable(eventType,
314 0 : tokenNameUTF16));
315 0 : NS_DispatchToMainThread(runnable);
316 0 : }
317 :
318 : //
319 : // This is the main loop.
320 : //
321 0 : void SmartCardMonitoringThread::Execute()
322 : {
323 : const char* tokenName;
324 :
325 : //
326 : // populate token names for already inserted tokens.
327 : //
328 0 : PK11SlotList* sl = PK11_FindSlotsByNames(mModule->dllName, nullptr, nullptr,
329 0 : true);
330 :
331 : PK11SlotListElement* sle;
332 0 : if (sl) {
333 0 : for (sle = PK11_GetFirstSafe(sl); sle;
334 : sle = PK11_GetNextSafe(sl, sle, false)) {
335 0 : SetTokenName(PK11_GetSlotID(sle->slot), PK11_GetTokenName(sle->slot),
336 0 : PK11_GetSlotSeries(sle->slot));
337 : }
338 0 : PK11_FreeSlotList(sl);
339 : }
340 :
341 : // loop starts..
342 : do {
343 : UniquePK11SlotInfo slot(
344 0 : SECMOD_WaitForAnyTokenEvent(mModule, 0, PR_SecondsToInterval(1)));
345 0 : if (!slot) {
346 0 : break;
347 : }
348 :
349 : // now we have a potential insertion or removal event, see if the slot
350 : // is present to determine which it is...
351 0 : if (PK11_IsPresent(slot.get())) {
352 : // insertion
353 0 : CK_SLOT_ID slotID = PK11_GetSlotID(slot.get());
354 0 : uint32_t series = PK11_GetSlotSeries(slot.get());
355 :
356 : // skip spurious insertion events...
357 0 : if (series != GetTokenSeries(slotID)) {
358 : // if there's a token name, then we have not yet issued a remove
359 : // event for the previous token, do so now...
360 0 : tokenName = GetTokenName(slotID);
361 0 : if (tokenName) {
362 0 : SendEvent(kSmartcardRemove, tokenName);
363 : }
364 0 : tokenName = PK11_GetTokenName(slot.get());
365 : // save the token name and series
366 0 : SetTokenName(slotID, tokenName, series);
367 0 : SendEvent(kSmartcardInsert, tokenName);
368 : }
369 : } else {
370 : // retrieve token name
371 0 : CK_SLOT_ID slotID = PK11_GetSlotID(slot.get());
372 0 : tokenName = GetTokenName(slotID);
373 : // if there's not a token name, then the software isn't expecting
374 : // a (or another) remove event.
375 0 : if (tokenName) {
376 0 : SendEvent(kSmartcardRemove, tokenName);
377 : // clear the token name (after we send it)
378 0 : SetTokenName(slotID, nullptr, 0);
379 : }
380 0 : }
381 : } while (1);
382 0 : }
383 :
384 : // accessor to help searching active Monitoring threads
385 0 : const SECMODModule* SmartCardMonitoringThread::GetModule()
386 : {
387 0 : return mModule;
388 : }
389 :
390 : // C-like calling sequence to glue into PR_CreateThread.
391 0 : void SmartCardMonitoringThread::LaunchExecute(void* arg)
392 : {
393 0 : AutoProfilerRegisterThread registerThread("SmartCard");
394 0 : NS_SetCurrentThreadName("SmartCard");
395 :
396 0 : ((SmartCardMonitoringThread*)arg)->Execute();
397 0 : }
|