Firefox · Networking
CVE-2026-109769
Logic Error in Networking
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifnetwerk/cache2/CacheIndex.cpp |
modified | |
CorruptDictionaryHashnetwerk/cache2/CacheStorageService.cpp |
modified | |
ifnetwerk/cache2/CacheStorageService.cpp |
modified | |
ClearDictionaryDataForTestingnetwerk/cache2/CacheStorageService.cpp |
modified | |
ifnetwerk/cache2/Dictionary.cpp |
modified | |
DictionaryCacheEntrynetwerk/cache2/Dictionary.cpp |
modified |
Files Changed
netwerk/cache2/CacheEntry.cppnetwerk/cache2/CacheIndex.cppnetwerk/cache2/CacheStorageService.cppnetwerk/cache2/Dictionary.cppnetwerk/cache2/Dictionary.hnetwerk/cache2/nsICacheTesting.idlnetwerk/protocol/http/nsHttpChannel.cppnetwerk/protocol/http/nsHttpHandler.cppnetwerk/test/unit/test_content_decoding_error_telemetry.jsnetwerk/test/unit/test_dictionary_oa_isolation.jsnetwerk/test/unit/test_dictionary_replacement.jsnetwerk/test/unit/xpcshell.toml
Patch
diff --git a/netwerk/cache2/CacheEntry.cpp b/netwerk/cache2/CacheEntry.cpp
index 16f248bddf7..c94b07ea23b 100644
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -1864,13 +1864,13 @@ void CacheEntry::DoomAlreadyRemoved() {
// Remove from DictionaryCache immediately, to ensure the removal is
// synchronous
LOG(("DoomAlreadyRemoved [entry=%p removed]", this));
- // These are keyed on the same ascii spec the entry itself is keyed on.
nsAutoCString uriSpec;
mURI->GetAsciiSpec(uriSpec);
+ nsCOMPtr<nsILoadContextInfo> lci = CacheFileUtils::ParseKey(mStorageID);
if (mEnhanceID.EqualsLiteral("dict:")) {
- DictionaryCache::RemoveOriginFor(uriSpec);
+ DictionaryCache::RemoveOriginFor(uriSpec, lci);
} else {
- DictionaryCache::RemoveDictionaryOMT(uriSpec);
+ DictionaryCache::RemoveDictionaryOMT(uriSpec, lci);
}
// Pretend pinning is know. This entry is now doomed for good, so don't
diff --git a/netwerk/cache2/CacheIndex.cpp b/netwerk/cache2/CacheIndex.cpp
index e0336db3643..a73f057ac2d 100644
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -897,7 +897,12 @@ nsresult CacheIndex::RemoveEntry(const SHA1Sum::Hash* aHash,
// CacheFileContextEvictor purges entries; they've already been cleared
// via CacheIndex::EvictByContext synchronously
if (aClearDictionary) {
- DictionaryCache::RemoveDictionaryOMT(aKey);
+ nsAutoCString uriSpec;
+ nsCOMPtr<nsILoadContextInfo> lci =
+ CacheFileUtils::ParseKey(aKey, nullptr, &uriSpec);
+ if (lci) {
+ DictionaryCache::RemoveDictionaryOMT(uriSpec, lci);
+ }
}
StaticMutexAutoLock lock(sLock);
diff --git a/netwerk/cache2/CacheStorageService.cpp b/netwerk/cache2/CacheStorageService.cpp
index 2be380aafab..826584e7b03 100644
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -12,10 +12,13 @@
#include "CacheLog.h"
#include "CacheObserver.h"
#include "CacheStorage.h"
+#include "Dictionary.h"
#include "ErrorList.h"
+#include "LoadContextInfo.h"
#include "mozilla/AtomicBitfields.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/OriginAttributes.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StoragePrincipalHelper.h"
@@ -2632,23 +2635,37 @@ CacheStorageService::ClearDictionaryCacheMemory() {
}
NS_IMETHODIMP
-CacheStorageService::CorruptDictionaryHash(const nsACString& aURI) {
+CacheStorageService::CorruptDictionaryHash(
+ const nsACString& aURI, JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx) {
LOG(("CacheStorageService::CorruptDictionaryHash [uri=%s]",
PromiseFlatCString(aURI).get()));
+ OriginAttributes attrs;
+ if (!attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
RefPtr<DictionaryCache> cache = DictionaryCache::GetInstance();
if (cache) {
- cache->CorruptHashForTesting(aURI);
+ RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, attrs);
+ cache->CorruptHashForTesting(aURI, lci);
}
return NS_OK;
}
NS_IMETHODIMP
-CacheStorageService::ClearDictionaryDataForTesting(const nsACString& aURI) {
+CacheStorageService::ClearDictionaryDataForTesting(
+ const nsACString& aURI, JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx) {
LOG(("CacheStorageService::ClearDictionaryDataForTesting [uri=%s]",
PromiseFlatCString(aURI).get()));
+ OriginAttributes attrs;
+ if (!attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
RefPtr<DictionaryCache> cache = DictionaryCache::GetInstance();
if (cache) {
- cache->ClearDictionaryDataForTesting(aURI);
+ RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, attrs);
+ cache->ClearDictionaryDataForTesting(aURI, lci);
}
return NS_OK;
}
diff --git a/netwerk/cache2/Dictionary.cpp b/netwerk/cache2/Dictionary.cpp
index e9c541ff726..03496b824ad 100644
--- a/netwerk/cache2/Dictionary.cpp
+++ b/netwerk/cache2/Dictionary.cpp
@@ -78,7 +78,6 @@ LazyLogModule gDictionaryLog("CompressionDictionaries");
* Reference to the DictionaryCache singleton. May be null.
*/
StaticRefPtr<DictionaryCache> gDictionaryCache;
-StaticRefPtr<nsICacheStorage> DictionaryCache::sCacheStorage;
Atomic<bool, Relaxed> DictionaryCache::sShutdown{false};
// about:cache gets upset about entries that don't fit URL specs, so we need
@@ -91,6 +90,36 @@ static nsresult GetDictPath(nsIURI* aURI, nsACString& aPrePath) {
return NS_OK;
}
+// static
+void DictionaryCache::MakeCacheKey(const nsACString& aPrePath,
+ nsILoadContextInfo* aLoadContextInfo,
+ nsACString& aKey) {
+ aKey.Truncate();
+ if (aLoadContextInfo) {
+ if (aLoadContextInfo->IsAnonymous()) {
+ aKey.AppendLiteral("A");
+ }
+ aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(aKey);
+ }
+ aKey.Append(aPrePath);
+}
+
+// static
+already_AddRefed<nsICacheStorage> DictionaryCache::GetCacheStorage(
+ nsILoadContextInfo* aLoadContextInfo) {
+ nsCOMPtr<nsICacheStorageService> cacheStorageService(
+ components::CacheStorage::Service());
+ if (!cacheStorageService) {
+ return nullptr;
+ }
+ nsCOMPtr<nsICacheStorage> storage;
+ if (NS_FAILED(cacheStorageService->DiskCacheStorage(
+ aLoadContextInfo, getter_AddRefs(storage)))) {
+ return nullptr;
+ }
+ return storage.forget();
+}
+
DictionaryCacheEntry::DictionaryCacheEntry(const char* aKey) {
mURI = aKey;
DICTIONARY_LOG(("Created DictionaryCacheEntry %p, uri=%s", this, aKey));
@@ -287,6 +316,16 @@ nsresult DictionaryCacheEntry::Prefetch(
DICTIONARY_LOG(("Prefetch for %s", mURI.get()));
+ // Refuse to serve dictionary bytes to a request from a different
+ // partition than the one this entry was added under.
+ if (mLoadContextInfo && aLoadContextInfo &&
+ !mLoadContextInfo->Equals(aLoadContextInfo)) {
+ DICTIONARY_LOG(
+ ("Prefetch for %s - refusing cross-partition access", mURI.get()));
+ aShouldSuspend = false;
+ return NS_ERROR_FAILURE;
+ }
+
// Determine private browsing status for this request
bool isPrivateBrowsing = aLoadContextInfo && aLoadContextInfo->IsPrivate();
@@ -314,18 +353,9 @@ nsresult DictionaryCacheEntry::Prefetch(
// already. Add to waiting list.
mWaitingPrefetch.push_back(PrefetchRequest{aFunc, isPrivateBrowsing});
- // We can't use sCacheStorage because we need the correct LoadContextInfo
- nsCOMPtr<nsICacheStorageService> cacheStorageService(
- components::CacheStorage::Service());
- if (!cacheStorageService) {
- mWaitingPrefetch.clear();
- aShouldSuspend = false;
- return NS_ERROR_FAILURE;
- }
- nsCOMPtr<nsICacheStorage> cacheStorage;
- nsresult rv = cacheStorageService->DiskCacheStorage(
- aLoadContextInfo, getter_AddRefs(cacheStorage));
- if (NS_FAILED(rv)) {
+ nsCOMPtr<nsICacheStorage> cacheStorage =
+ DictionaryCache::GetCacheStorage(aLoadContextInfo);
+ if (!cacheStorage) {
mWaitingPrefetch.clear();
aShouldSuspend = false;
return NS_ERROR_FAILURE;
@@ -820,7 +850,8 @@ DictionaryCacheEntry::OnStopRequest(nsIRequest* request, nsresult result) {
self->mStopReceived = true;
if (shouldRemoveDictionary) {
// Already on MainThread
- DictionaryCache::RemoveDictionary(self->mURI);
+ DictionaryCache::RemoveDictionary(self->mURI,
+ self->GetLoadContextInfo());
}
});
NS_DispatchToMainThread(runnable);
@@ -837,6 +868,11 @@ void DictionaryCacheEntry::UnblockAddEntry(DictionaryOrigin* aOrigin) {
mBlocked = false;
}
+void DictionaryCacheEntry::SetOrigin(DictionaryOrigin* aOrigin) {
+ mOrigin = aOrigin;
+ mLoadContextInfo = aOrigin ? aOrigin->GetLoadContextInfo() : nullptr;
+}
+
void DictionaryCacheEntry::WriteOnHash() {
MOZ_ASSERT(NS_IsMainThread());
if (!mHash.IsEmpty() && mOrigin) {
@@ -900,12 +936,13 @@ DictionaryCacheEntry::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
entry->OpenInputStream(0, getter_AddRefs(stream));
if (!stream) {
DICTIONARY_LOG(("OpenInputStream failed for %s", mURI.get()));
- nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
- "DictionaryCacheEntry::OnCacheEntryAvailable",
- [self = RefPtr{this}]() {
- self->CleanupOnCacheData(NS_ERROR_FAILURE);
- DictionaryCache::RemoveDictionary(self->mURI);
- });
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction("DictionaryCacheEntry::OnCacheEntryAvailable",
+ [self = RefPtr{this}]() {
+ self->CleanupOnCacheData(NS_ERROR_FAILURE);
+ DictionaryCache::RemoveDictionary(
+ self->mURI, self->GetLoadContextInfo());
+ });
NS_DispatchToMainThread(runnable);
return NS_OK;
}
@@ -918,23 +955,25 @@ DictionaryCacheEntry::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
nsresult rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream);
if (NS_FAILED(rv)) {
DICTIONARY_LOG(("nsInputStreamPump::Create failed for %s", mURI.get()));
- NS_DispatchToMainThread(NS_NewRunnableFunction(
- "DictionaryCacheEntry::OnCacheEntryAvailable",
- [self = RefPtr{this}]() {
- self->CleanupOnCacheData(NS_ERROR_FAILURE);
- DictionaryCache::RemoveDictionary(self->mURI);
- }));
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("DictionaryCacheEntry::OnCacheEntryAvailable",
+ [self = RefPtr{this}]() {
+ self->CleanupOnCacheData(NS_ERROR_FAILURE);
+ DictionaryCache::RemoveDictionary(
+ self->mURI, self->GetLoadContextInfo());
+ }));
return NS_OK;
}
rv = pump->AsyncRead(this);
if (NS_FAILED(rv)) {
DICTIONARY_LOG(("AsyncRead failed for %s", mURI.get()));
- NS_DispatchToMainThread(NS_NewRunnableFunction(
- "DictionaryCacheEntry::OnCacheEntryAvailable",
- [self = RefPtr{this}]() {
- self->CleanupOnCacheData(NS_ERROR_FAILURE);
- DictionaryCache::RemoveDictionary(self->mURI);
- }));
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("DictionaryCacheEntry::OnCacheEntryAvailable",
+ [self = RefPtr{this}]() {
+ self->CleanupOnCacheData(NS_ERROR_FAILURE);
+ DictionaryCache::RemoveDictionary(
+ self->mURI, self->GetLoadContextInfo());
+ }));
return NS_OK;
}
DICTIONARY_LOG(("Waiting for data"));
@@ -950,7 +989,8 @@ DictionaryCacheEntry::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
"DictionaryCacheEntry::OnCacheEntryAvailable",
[self = RefPtr{this}, uriCopy]() {
self->CleanupOnCacheData(NS_ERROR_CORRUPTED_CONTENT);
- DictionaryCache::RemoveDictionary(self->mURI);
+ DictionaryCache::RemoveDictionary(self->mURI,
+ self->GetLoadContextInfo());
});
NS_DispatchToMainThread(runnable);
}
@@ -964,7 +1004,8 @@ DictionaryCacheEntry::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
// as needed. If aType is TYPE_OTHER, there is no Match() to do
void DictionaryOriginReader::Start(
bool aCreate, DictionaryOrigin* aOrigin, nsACString& aKey, nsIURI* aURI,
- ExtContentPolicyType aType, DictionaryCache* aCache,
+ ExtContentPolicyType aType, nsILoadContextInfo* aLoadContextInfo,
+ DictionaryCache* aCache,
const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback) {
mOrigin = aOrigin;
mURI = aURI;
@@ -985,14 +1026,21 @@ void DictionaryOriginReader::Start(
if (mOrigin->mWaitingCacheRead.Length() == 1) { // was empty
DICTIONARY_LOG(("DictionaryOriginReader::Start(%s): %p",
PromiseFlatCString(aKey).get(), this));
- DictionaryCache::sCacheStorage->AsyncOpenURIString(
- aKey, META_DICTIONARY_PREFIX,
- aCreate
- ? nsICacheStorage::OPEN_NORMALLY |
- nsICacheStorage::CHECK_MULTITHREADED
- : nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
- nsICacheStorage::CHECK_MULTITHREADED,
- this);
+ nsCOMPtr<nsICacheStorage> storage =
+ DictionaryCache::GetCacheStorage(aLoadContextInfo);
+ if (!storage) {
+ mOrigin->mWaitingCacheRead.Clear();
+ (aCallback)(true, nullptr);
+ return;
+ }
+ storage->AsyncOpenURIString(aKey, META_DICTIONARY_PREFIX,
+ aCreate
+ ? nsICacheStorage::OPEN_NORMALLY |
+ nsICacheStorage::CHECK_MULTITHREADED
+ : nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED,
+ this);
// This one will get the direct callback to do Match()
}
// Else we already have a read for this cache entry pending, just wait
@@ -1116,13 +1164,6 @@ nsresult DictionaryCache::Init() {
if (!cacheStorageService) {
return NS_ERROR_FAILURE;
}
- nsCOMPtr<nsICacheStorage> temp;
- nsresult rv = cacheStorageService->DiskCacheStorage(
- nullptr, getter_AddRefs(temp)); // Don't need a load context
- if (NS_FAILED(rv)) {
- return rv;
- }
- sCacheStorage = temp;
nsCOMPtr<nsIObserverService> obsService =
mozilla::services::GetObserverService();
@@ -1130,7 +1171,7 @@ nsresult DictionaryCache::Init() {
obsService->AddObserver(this, "idle-daily", false);
}
}
- DICTIONARY_LOG(("Inited DictionaryCache %p", sCacheStorage.get()));
+ DICTIONARY_LOG(("Inited DictionaryCache"));
return NS_OK;
}
@@ -1145,7 +1186,6 @@ void DictionaryCache::Shutdown() {
}
}
gDictionaryCache = nullptr;
- sCacheStorage = nullptr;
}
NS_IMETHODIMP
@@ -1165,13 +1205,11 @@ DictionaryCache::Observe(nsISupports* subject, const char* topic,
NS_IMPL_ISUPPORTS(DictionaryCache, nsIObserver)
-nsresult DictionaryCache::AddEntry(nsIURI* aURI, const nsACString& aKey,
- const nsACString& aPattern,
- nsTArray<nsCString>& aMatchDest,
- const nsACString& aId,
- const Maybe<nsCString>& aHash,
- bool aNewEntry, uint32_t aExpiration,
- DictionaryCacheEntry** aDictEntry) {
+nsresult DictionaryCache::AddEntry(
+ nsIURI* aURI, const nsACString& aKey, const nsACString& aPattern,
+ nsTArray<nsCString>& aMatchDest, const nsACString& aId,
+ const Maybe<nsCString>& aHash, bool aNewEntry, uint32_t aExpiration,
+ nsILoadContextInfo* aLoadContextInfo, DictionaryCacheEntry** aDictEntry) {
// Note that normally we're getting an entry in and until all the data
// has been received, we can't use it. The Hash being null is a flag
// that it's not yet valid.
@@ -1182,7 +1220,7 @@ nsresult DictionaryCache::AddEntry(nsIURI* aURI, const nsACString& aKey,
// Note that we don't know if there's an entry for this key in the origin
RefPtr<DictionaryCacheEntry> dict = new DictionaryCacheEntry(
aKey, aPattern, aMatchDest, aId, aExpiration, aHash);
- dict = AddEntry(aURI, aNewEntry, dict);
+ dict = AddEntry(aURI, aNewEntry, aLoadContextInfo, dict);
if (dict) {
*aDictEntry = do_AddRef(dict).take();
return NS_OK;
@@ -1194,7 +1232,8 @@ nsresult DictionaryCache::AddEntry(nsIURI* aURI, const nsACString& aKey,
}
already_AddRefed<DictionaryCacheEntry> DictionaryCache::AddEntry(
- nsIURI* aURI, bool aNewEntry, DictionaryCacheEntry* aDictEntry) {
+ nsIURI* aURI, bool aNewEntry, nsILoadContextInfo* aLoadContextInfo,
+ DictionaryCacheEntry* aDictEntry) {
// Note that normally we're getting an entry in and until all the data
// has been received, we can't use it. The Hash being null is a flag
// that it's not yet valid.
@@ -1202,26 +1241,32 @@ already_AddRefed<DictionaryCacheEntry> DictionaryCache::AddEntry(
if (NS_FAILED(GetDictPath(aURI, prepath))) {
return nullptr;
}
- DICTIONARY_LOG(
- ("AddEntry: %s, %d, %p", prepath.get(), aNewEntry, aDictEntry));
+ nsCString mapKey;
+ MakeCacheKey(prepath, aLoadContextInfo, mapKey);
+ DICTIONARY_LOG(("AddEntry: %s, %d, %p", mapKey.get(), aNewEntry, aDictEntry));
+ // SetOrigin() below only runs when the origin is newly created.
+ aDictEntry->SetLoadContextInfo(aLoadContextInfo);
// create for the origin if it doesn't exist
RefPtr<DictionaryCacheEntry> newEntry;
- (void)mDictionaryCache.WithEntryHandle(prepath, [&](auto&& entry) {
... (file diff truncated)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/netwerk/test/unit/test_content_decoding_error_telemetry.js b/netwerk/test/unit/test_content_decoding_error_telemetry.js
index af62153b3c3..1e6c9a29c3a 100644
--- a/netwerk/test/unit/test_content_decoding_error_telemetry.js
+++ b/netwerk/test/unit/test_content_decoding_error_telemetry.js
@@ -6,6 +6,7 @@ const { NodeHTTPSServer } = ChromeUtils.importESModule(
let httpServer;
let dictUrl;
+let dictOriginAttributes;
function makeChan(url) {
let chan = NetUtil.newChannel({
@@ -16,6 +17,13 @@ function makeChan(url) {
return chan;
}
+// Returns the OriginAttributes a completed channel's request was
+// partitioned under, for use with nsICacheTesting's dictionary methods.
+function originAttributesForChannel(chan) {
+ let partitionKey = chan.loadInfo.cookieJarSettings.partitionKey;
+ return partitionKey ? { partitionKey } : {};
+}
+
function channelOpenPromise(chan) {
return new Promise(resolve => {
chan.asyncOpen(
@@ -66,6 +74,7 @@ add_setup(async function setup() {
let chan = makeChan(dictUrl);
await channelOpenPromise(chan);
+ dictOriginAttributes = originAttributesForChannel(chan);
});
async function registerBadDCBEndpoint(httpServer, path, badPayload) {
@@ -219,7 +228,7 @@ add_task(async function test_dict_hash_mismatch_telemetry() {
Services.fog.testResetFOG();
let cacheTesting = Services.cache2.QueryInterface(Ci.nsICacheTesting);
- cacheTesting.corruptDictionaryHash(dictUrl);
+ cacheTesting.corruptDictionaryHash(dictUrl, dictOriginAttributes);
Services.obs.notifyObservers(null, "clear-dictionary-data", dictUrl);
diff --git a/netwerk/test/unit/test_dictionary_oa_isolation.js b/netwerk/test/unit/test_dictionary_oa_isolation.js
new file mode 100644
index 00000000000..980687cc13a
--- /dev/null
+++ b/netwerk/test/unit/test_dictionary_oa_isolation.js
@@ -0,0 +1,136 @@
+/**
+ * Verifies that the DictionaryCache is partitioned by OriginAttributes /
+ * LoadContextInfo: a compression dictionary registered by one partition
+ * (e.g. a container) must not be offered to requests from a different
+ * partition, even for the exact same origin and matching path.
+ */
+
+"use strict";
+
+Services.scriptloader.loadSubScript("resource://test/head_cache.js", this);
+
+const { NodeHTTPSServer } = ChromeUtils.importESModule(
+ "resource://testing-common/NodeServer.sys.mjs"
+);
+
+let server = null;
+
+add_setup(async function () {
+ Services.prefs.setBoolPref("network.http.dictionaries.enable", true);
+
+ server = new NodeHTTPSServer();
+ await server.start();
+
+ registerCleanupFunction(async () => {
+ try {
+ await server.stop();
+ } catch (e) {
+ // Ignore server stop errors during cleanup
+ }
+ Services.prefs.clearUserPref("network.http.dictionaries.enable");
+ });
+});
+
+function makeChan(url, userContextId) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadInfo.originAttributes = { userContextId };
+ return chan;
+}
+
+function channelOpenPromise(chan) {
+ return new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buffer) => resolve([req, buffer]),
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+ });
+}
+
+function syncCache() {
+ return new Promise(resolve => {
+ syncWithCacheIOThread(resolve, true);
+ });
+}
+
+add_task(async function test_dictionary_cache_isolated_by_origin_attributes() {
+ await server.registerPathHandler("/dict1", function (request, response) {
+ response.writeHead(200, {
+ "Content-Type": "application/octet-stream",
+ "Use-As-Dictionary": `match="/content*", id="dict-container-1", type=raw`,
+ "Cache-Control": "max-age=3600",
+ });
+ response.end("DICTIONARY_CONTENT_FOR_CONTAINER_1", "binary");
+ });
+
+ await server.registerPathHandler("/dict2", function (request, response) {
+ response.writeHead(200, {
+ "Content-Type": "application/octet-stream",
+ "Use-As-Dictionary": `match="/content*", id="dict-container-2", type=raw`,
+ "Cache-Control": "max-age=3600",
+ });
+ response.end("DICTIONARY_CONTENT_FOR_CONTAINER_2", "binary");
+ });
+
+ await server.registerPathHandler("/content", function (request, response) {
+ global.lastAvailableDictionary =
+ request.headers["available-dictionary"] || null;
+ global.lastDictionaryId = request.headers["dictionary-id"] || null;
+ response.writeHead(200, {
+ "Content-Type": "text/plain",
+ "Cache-Control": "no-cache",
+ });
+ response.end("PLAIN_CONTENT", "binary");
+ });
+
+ let dict1Url = `https://localhost:${server.port()}/dict1`;
+ let dict2Url = `https://localhost:${server.port()}/dict2`;
+ let contentUrl = `https://localhost:${server.port()}/content`;
+
+ // Register a dictionary in container 1, and a different one -- matching
+ // the same path pattern -- in container 2.
+ await channelOpenPromise(makeChan(dict1Url, 1));
+ await channelOpenPromise(makeChan(dict2Url, 2));
+ await syncCache();
+
+ async function lastOfferedDictionaryId(userContextId) {
+ await server.execute(
+ "global.lastAvailableDictionary = null; global.lastDictionaryId = null;"
+ );
+ await channelOpenPromise(makeChan(contentUrl, userContextId));
+ return server.execute("global.lastDictionaryId");
+ }
+
+ Assert.equal(
+ await lastOfferedDictionaryId(1),
+ '"dict-container-1"',
+ "Container 1 should be offered its own dictionary"
+ );
+
+ Assert.equal(
+ await lastOfferedDictionaryId(2),
+ '"dict-container-2"',
+ "Container 2 should be offered its own dictionary, not container 1's"
+ );
+
+ Assert.equal(
+ await lastOfferedDictionaryId(3),
+ null,
+ "A container with no registered dictionary must not be offered one " +
+ "from a different partition"
+ );
+
+ // Re-check container 1 again, to make sure the intervening requests from
+ // other partitions didn't clobber or replace its dictionary.
+ Assert.equal(
+ await lastOfferedDictionaryId(1),
+ '"dict-container-1"',
+ "Container 1 should still be offered its own dictionary"
+ );
+});
diff --git a/netwerk/test/unit/test_dictionary_replacement.js b/netwerk/test/unit/test_dictionary_replacement.js
index e976bd68171..bec5507a18b 100644
--- a/netwerk/test/unit/test_dictionary_replacement.js
+++ b/netwerk/test/unit/test_dictionary_replacement.js
@@ -58,6 +58,13 @@ function makeChan(url, bypassCache = false) {
return chan;
}
+// Returns the OriginAttributes a completed channel's request was
+// partitioned under, for use with nsICacheTesting's dictionary methods.
+function originAttributesForChannel(chan) {
+ let partitionKey = chan.loadInfo.cookieJarSettings.partitionKey;
+ return partitionKey ? { partitionKey } : {};
+}
+
function channelOpenPromise(chan, intermittentFail = false) {
return new Promise(resolve => {
function finish(req, buffer) {
@@ -582,6 +589,7 @@ add_task(async function test_dictionary_hash_mismatch() {
let dictUrl = `https://localhost:${server.port()}/dict/hash-test`;
let chan = makeChan(dictUrl);
let [, data] = await channelOpenPromise(chan);
+ let dictOriginAttributes = originAttributesForChannel(chan);
Assert.equal(
data,
@@ -608,14 +616,14 @@ add_task(async function test_dictionary_hash_mismatch() {
dump("**** Step 3: Corrupt the dictionary hash\n");
- testingInterface.corruptDictionaryHash(dictUrl);
+ testingInterface.corruptDictionaryHash(dictUrl, dictOriginAttributes);
dump("**** Step 4: Clear dictionary data to force reload from disk\n");
// Clear dictionary data while keeping the corrupted hash.
// When next prefetch happens, data will be reloaded and compared
// against the corrupted hash, causing a mismatch.
- testingInterface.clearDictionaryDataForTesting(dictUrl);
+ testingInterface.clearDictionaryDataForTesting(dictUrl, dictOriginAttributes);
dump(
"**** Step 5: Request matching resource - should fail due to hash mismatch\n"
diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml
index bee57222dc8..1b029a6e29e 100644
--- a/netwerk/test/unit/xpcshell.toml
+++ b/netwerk/test/unit/xpcshell.toml
@@ -517,10 +517,10 @@ prefs = ["browser.cache.disk.encryption.enabled=true"]
["test_cache2-43-purge-residual-folder.js"]
-["test_cache2-44-no-rewrite-unchanged-metadata.js"]
-
["test_cache2-44-clear-origin-context-evictor.js"]
+["test_cache2-44-no-rewrite-unchanged-metadata.js"]
+
["test_cache2-45-clear-base-domain-context-evictor.js"]
["test_cache2_clear_with_principal.js"]
@@ -658,6 +658,9 @@ prefs = ["content.cors.use_triggering_principal=true"] # See bug 1982916.
["test_dictionary_compression_dcb.js"]
run-sequentially = ["true"]
+["test_dictionary_oa_isolation.js"]
+run-sequentially = ["true"]
+
["test_dictionary_replacement.js"]
run-sequentially = ["true"]
Loading diff…
References
On This Page