Closed Bug 124923 Opened 23 years ago Closed 21 years ago

The dynamic oid hash table (oid_d_hash in secoid.c) is not thread-safe

Categories

(NSS :: Libraries, defect, P2)

defect

Tracking

(Not tracked)

RESOLVED FIXED

People

(Reporter: wtc, Assigned: nelson)

References

Details

Attachments

(1 file, 1 obsolete file)

In secoid.c, there are two hash tables for OIDs. One is static (oidhash), and the other is dynamic (oid_d_hash). The static table (oidhash) is never changed after it has been constructed. Therefore, we can do lookups in the static table with PL_HashTableLookupConst without using a lock. The dynamic table (oid_d_hash) gets modified (new entries added) when the user loads new crypto modules. It needs to be protected by a lock. Bob, do we need to fix this in NSS 3.4?
Assigned the bug to Bishakha.
Assignee: wtc → bishakhabanerjee
Priority: -- → P2
Target Milestone: --- → 3.4.1
Changed the QA contact to Bishakha.
QA Contact: sonja.mirtitsch → bishakhabanerjee
Set target milestone to NSS 3.5.
Target Milestone: 3.4.1 → 3.5
Moved to 3.7.
Assignee: bishakhabanerjee → wtc
Target Milestone: 3.5 → 3.7
Moved to target milestone 3.8 because the original NSS 3.7 release has been renamed 3.8.
Target Milestone: 3.7 → 3.8
Remove target milestone of 3.8, since these bugs didn't get into that release.
Target Milestone: 3.8 → ---
I plan to fix this feature for NSS 3.10, so that pp and certutil can use it.
Assignee: wchang0222 → MisterSSL
Blocks: 132942
Target Milestone: --- → 3.10
Here are a number of related issues that I intend to fix while fixing this bug: 1. SECOID_AddEntry must be declared in a public header file. 2. SECOID_AddEntry must be exported. 3. SECOID_AddEntry must update the hash table to include the new entry. 4. There must be some way for the caller of SECOID_AddEntry to obtain the new SECOidTag of the new dynamicly allocated entry. One way to do this is for SECOID_AddEntry to return the new SECOidTag, rather than a SECStatus. It could return SEC_OID_UNKNOWN for failure. 5. SECOID_AddEntry should take arguments to allow the mechanism and supportedExtension members of the struct SECOidDataStr to be properly initialized. Perhaps it should just take a struct SECOidDataStr as its input argument, and set the SECOidTag in the "offset" member of that struct. 4. The existing code in SECOID_Shutdown leaks memory allocated for the OID and the OID descriptions, which are pointed to by the dynamic hash table entry. All the memory allocated by SECOID_AddEntry should be allocated from a PLArenaPool. SECOID_Shutdown should free that ArenaPool. 5. I intend to use NSS reader/writer locks to protect the dynamic OID table and the associate PL Hash table.
This code is a near total rewrite of the code that maintains and queries the table of dynamic OIDs. It work in my tests, by I haven't done multi-threaded testing yet.
Depends on: 231881
Comment on attachment 139967 [details] [diff] [review] patch v1 - reimplement dynamic OID code Please review. This patch assumes that the patch for bug 231881 is already applied (or checked in).
Attachment #139967 - Flags: review?(rrelyea0264)
Blocks: 196360
Patch v1 above provides the same functionality as the original code, but in a thread safe manner, and without leaks. However, it occurs to me that this code is missing some basic sanity checks on the input, which are not added in patch v1, such as: - no check to see if the OID being added is already present - no check to see if the description has non-zero length - no check to see if the supportedExtension member has a valid value. So, I will prepare a patch part 2, to be applied on top of patch v1 above, to add the missing checks.
Status: NEW → ASSIGNED
No longer depends on: 231881
Comment on attachment 139967 [details] [diff] [review] patch v1 - reimplement dynamic OID code First, this patch is generally good, I have two comments (one minor). There are lots of places throughout the code where the following code appears: if (!dynOid && secoid_InitDynOidData() != SECSuccess) { return SECFailure; } The comment in Shutdown() seems to indicate on first reading that we don't always initialize the Dynamic OID system. Is this the intent, or does the comment really mean "Well if we get here, we've failed to ever initialize the oid system, even after serveral attempts (including this one)"? It seems to me that in most places we make this call, we could simply check the dynOidLock. If it doesn't exist we need not continue. 2 exceptions are: 1) the call in SECOID_AddEntry would naturally need to initalize the DynOidData, and 2) The call in secoid_init(). It appears this is to produce the side effect of serializing access to secoid_init()?, but I'm not so sure it accomplishes this? The other commement is related to that test. No Error code is set in any of the failure cases. This is a minor comment because the code that this replaces also set no error code in the equivalent cases.
Attachment #139967 - Flags: review?(rrelyea0264) → review-
This patch is quite a bit different than the previous one. I included both unified and context diffs, to try to make it easier to review. I think this addresses all the review comments. It may generate some new ones :)
Attachment #139967 - Attachment is obsolete: true
Comment on attachment 140130 [details] [diff] [review] patch v2 - unified and context diffs Bob, please review again. Beware, This patch is quite a bit different. Thanks.
Attachment #140130 - Flags: review?(rrelyea0264)
Blocks: 210584
No longer blocks: 196360
*** Bug 196360 has been marked as a duplicate of this bug. ***
Comment on attachment 140130 [details] [diff] [review] patch v2 - unified and context diffs Yes, looks good. Two minor nits in the comments: imiiialized -> initialized lcok ->lock
Attachment #140130 - Flags: review?(rrelyea0264) → review+
Comment on attachment 140130 [details] [diff] [review] patch v2 - unified and context diffs 1. SECOID_AddEntry doesn't need to call secoid_InitDynOidData because NSS initialization calls secoid_Init, which calls secoid_InitDynOidData. Remove lines 2-4 in the comment before the definition of secoid_InitDynOidData. 2. secoid_Init should call secoid_InitDynOidData as follows: if (oidhash) { return SECSuccess; } + + if (secoid_InitDynOidData() != SECSuccess) { + return SECFailure; + } We might want to rename secoid_Init as SECOID_Init and declare it in secoid.h. 3. In secoid_InitDynOidData, dynOidLock should be created with a NSSRWLock_New call. When we create dynOidPool, we don't need to lock dynOidLock. 4. The old comment "Worry: what about thread safety..." should be deleted. 5. In secoid_FindDynamic and secoid_FindDynamicByTag, testing dynOidTable without a lock is not thread-safe. This "double-checked locking" pattern is now considered broken. See http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html or search for "double checked locking" in the comp.programming.threads newsgroup, for articles written by David Butenhof. 6. In secoid_FindDynamic, we should test dynOidHash, not dynOidTable, before the PL_HashTableLookup call, right? 7. In SECOID_Shutdown, you don't need to lock dynOidLock when you destroy the data structures. This can simplify the code.
I checked patch v2 in before I received wtc's comments (by email). I will address the latest review comments in a separate patch/checkin. /cvsroot/mozilla/security/nss/lib/util/secasn1t.h,v <-- secasn1t.h new revision: 1.8; previous revision: 1.7 /cvsroot/mozilla/security/nss/lib/util/secoid.c,v <-- secoid.c new revision: 1.26; previous revision: 1.25 /cvsroot/mozilla/security/nss/lib/util/secoid.h,v <-- secoid.h new revision: 1.5; previous revision: 1.4
I disagree with some of the suggestions in comment 17. Let me take them point by point. 1. I deliberately coded this in a way that does not depend on secoid_Init calling secoid_InitDynOidData. This is defensive programming against future changes. Comment lines 2-4 before secoid_InitDynOidData are there to discourage subsequent programmers from changing the code to the more obvious (but wrong) test. 2. What if secoid_Init gets called twice? 3. creating dynOidLock with a NSSRWLock_New call would not be thread safe. Nothing in this function or in secoid_Init prevents this function from being called simultaneously by two threads. That is precisely why nssRWLock_AtomicCreate exists. 4. done. 5. The situation described in the cited paper requires that the code inside the double-checked block *create* the object that was not found. It is not safe precisely because the code inside the double-checked code block alters the condition that is checked. However, the code in secoid_FindDynamic and secoid_FindDynamicByTag does NOT alter the double-checked value, and so is not vulnerable to the issues described in that article. 6. Yes. I'll change it. 7. If all code in NSS (and code that uses it) was correct with respect to locks, I would agree. However, I have a great deal of experience with locking issues in NSS (and apps that use it) that has taught me to take extra defensive precautions against ill-behaved code, especially in the area of destroying locks and the data they protect. You'll see this same sort of login in SSL too.
/cvsroot/mozilla/security/nss/lib/util/secoid.c,v <-- secoid.c new revision: 1.27; previous revision: 1.26 Made (some) changes suggested in above review comments.
Status: ASSIGNED → RESOLVED
Closed: 21 years ago
Resolution: --- → FIXED
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: