Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is localStorage thread safe?

I'm curious about the possibility of damaging localStorage entry by overwriting it in two browser tabs simultaneously. Should I create a mutex for local storage?
I was already thinking of such pseudo-class:

LocalStorageMan.prototype.v = LocalStorageMan.prototype.value = function(name, val) {   //Set inner value   this.data[name] = val;   //Delay any changes if the local storage is being changed   if(localStorage[this.name+"__mutex"]==1) {     setTimeout(function() {this.v(name, val);}, 1);     return null;  //Very good point @Lightness Races in Orbit    }   //Lock the mutext to prevent overwriting   localStorage[this.name+"__mutex"] = 1;   //Save serialized data   localStorage[this.name] = this.serializeData;   //Allow usage from another tabs   localStorage[this.name+"__mutex"] = 0; } 

The function above implies local storage manager that is managing one specific key of the local storage - localStorage["test"] for example. I want to use this for greasomonkey userscripts where avoiding conlicts is a priority.

like image 369
Tomáš Zato - Reinstate Monica Avatar asked Feb 24 '14 23:02

Tomáš Zato - Reinstate Monica


People also ask

Can localStorage be hacked?

On the downside, localStorage is potentially vulnerable to cross-site scripting (XSS) attacks. If an attacker can inject malicious JavaScript into a webpage, they can steal an access token in localStorage. Also, unlike cookies, localStorage doesn't provide secure attributes that you can set to block attacks.

Is it secure to use localStorage?

No. localStorage is accessible by any webpage, and if you have the key, you can change whatever data you want. That being said, if you can devise a way to safely encrypt the keys, it doesn't matter how you transfer the data, if you can contain the data within a closure, then the data is (somewhat) safe.

Why we should not use localStorage?

Limitations & considerations to use local storage: It is not secure, can be accessed by browser developer tools, so don't use it to store sensitive data. It can be cleared by the user when he/she clears all browser history. It can only store string data.

Does localStorage only store strings?

LocalStorage is a key/value datastore that's available on a user's browser. Like cookies, LocalStorage can only store string data for its keys and values.


1 Answers

Yes, it is thread safe. However, your code isn't atomic and that's your problem there. I'll get to thread safety of localStorage but first, how to fix your problem.

Both tabs can pass the if check together and write to the item overwriting each other. The correct way to handle this problem is using StorageEvents.

These let you notify other windows when a key has changed in localStorage, effectively solving the problem for you in a built in message passing safe way. Here is a nice read about them. Let's give an example:

// tab 1 localStorage.setItem("Foo","Bar");  // tab 2 window.addEventListener("storage",function(e){     alert("StorageChanged!"); // this will run when the localStorage is changed }); 

Now, what I promised about thread safety :)

As I like - let's observe this from two angles - from the specification and using the implementation.

The specification

Let's show it's thread safe by specification.

If we check the specification of Web Storage we can see that it specifically notes:

Because of the use of the storage mutex, multiple browsing contexts will be able to access the local storage areas simultaneously in such a manner that scripts cannot detect any concurrent script execution.

Thus, the length attribute of a Storage object, and the value of the various properties of that object, cannot change while a script is executing, other than in a way that is predictable by the script itself.

It even elaborates further:

Whenever the properties of a localStorage attribute's Storage object are to be examined, returned, set, or deleted, whether as part of a direct property access, when checking for the presence of a property, during property enumeration, when determining the number of properties present, or as part of the execution of any of the methods or attributes defined on the Storage interface, the user agent must first obtain the storage mutex.

Emphasis mine. It also notes that some implementors don't like this as a note.

In practice

Let's show it's thread safe in implementation.

Choosing a random browser, I chose WebKit (because I didn't know where that code is located there before). If we check at WebKit's implementation of Storage we can see that it has its fare share of mutexes.

Let's take it from the start. When you call setItem or assign, this happens:

void Storage::setItem(const String& key, const String& value, ExceptionCode& ec) {     if (!m_storageArea->canAccessStorage(m_frame)) {         ec = SECURITY_ERR;         return;     }      if (isDisabledByPrivateBrowsing()) {         ec = QUOTA_EXCEEDED_ERR;         return;     }      bool quotaException = false;     m_storageArea->setItem(m_frame, key, value, quotaException);      if (quotaException)         ec = QUOTA_EXCEEDED_ERR; } 

Next, this happens in StorageArea:

void StorageAreaImpl::setItem(Frame* sourceFrame, const String& key, const String& value, bool& quotaException) {     ASSERT(!m_isShutdown);     ASSERT(!value.isNull());     blockUntilImportComplete();      String oldValue;     RefPtr<StorageMap> newMap = m_storageMap->setItem(key, value, oldValue, quotaException);     if (newMap)         m_storageMap = newMap.release();      if (quotaException)         return;      if (oldValue == value)         return;      if (m_storageAreaSync)         m_storageAreaSync->scheduleItemForSync(key, value);      dispatchStorageEvent(key, oldValue, value, sourceFrame); } 

Note that blockUntilImportComplete here. Let's look at that:

void StorageAreaSync::blockUntilImportComplete() {     ASSERT(isMainThread());      // Fast path.  We set m_storageArea to 0 only after m_importComplete being true.     if (!m_storageArea)         return;      MutexLocker locker(m_importLock);     while (!m_importComplete)         m_importCondition.wait(m_importLock);     m_storageArea = 0; } 

They also went as far as add a nice note:

// FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so). // Blocking everything until the import is complete is by far the simplest and safest thing to do, but // there is certainly room for safe optimization: Key/length will never be able to make use of such an // optimization (since the order of iteration can change as items are being added). Get can return any // item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list // of items the import should not overwrite. Clear can also work, but it'll need to kill the import // job first. 

Explaining this works, but it can be more efficient.

like image 162
Benjamin Gruenbaum Avatar answered Sep 17 '22 02:09

Benjamin Gruenbaum