Files
sousa-gecko/startupcache/StartupCacheUtils.cpp
T
Mike Conley 0e7692507c Bug 2012009 - Part 1: Don't retrieve scripts from non omni.ja JAR files from the ScriptPreloader or StartupCache. r=tcampbell
System add-ons can be installed in the profile directory as .xpi JAR files that
are updated independently of the application. Prior to this patch, when these JARs
are updated independently from the rest of the application, the ScriptPreloader
and StartupCache can serve stale bytecode compiled from the previous version of
the JAR, causing the old code to execute even though the JAR has been replaced.

This fix here adds a helper to checking if a script's JAR file is in one of
the omni.ja files before using cached bytecode.

Scripts from the omni.ja files continue to benefit from bytecode caching as
before.

Differential Revision: https://phabricator.services.mozilla.com/D280333
2026-01-28 15:18:42 +00:00

240 lines
7.3 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsCOMPtr.h"
#include "nsIInputStream.h"
#include "nsNetUtil.h"
#include "nsIFileURL.h"
#include "nsIJARURI.h"
#include "nsISubstitutingProtocolHandler.h"
#include "nsIChromeRegistry.h"
#include "nsStringStream.h"
#include "StartupCacheUtils.h"
#include "mozilla/scache/StartupCache.h"
#include "mozilla/Omnijar.h"
namespace mozilla {
namespace scache {
nsresult NewObjectInputStreamFromBuffer(const char* buffer, uint32_t len,
nsIObjectInputStream** stream) {
nsCOMPtr<nsIInputStream> stringStream;
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream),
Span(buffer, len), NS_ASSIGNMENT_DEPEND);
MOZ_ALWAYS_SUCCEEDS(rv);
nsCOMPtr<nsIObjectInputStream> objectInput =
NS_NewObjectInputStream(stringStream);
objectInput.forget(stream);
return NS_OK;
}
nsresult NewObjectOutputWrappedStorageStream(
nsIObjectOutputStream** wrapperStream, nsIStorageStream** stream,
bool wantDebugStream) {
nsCOMPtr<nsIStorageStream> storageStream;
nsresult rv =
NS_NewStorageStream(256, UINT32_MAX, getter_AddRefs(storageStream));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(storageStream);
nsCOMPtr<nsIObjectOutputStream> objectOutput =
NS_NewObjectOutputStream(outputStream);
#ifdef DEBUG
if (wantDebugStream) {
// Wrap in debug stream to detect unsupported writes of
// multiply-referenced non-singleton objects
StartupCache* sc = StartupCache::GetSingleton();
NS_ENSURE_TRUE(sc, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIObjectOutputStream> debugStream;
sc->GetDebugObjectOutputStream(objectOutput, getter_AddRefs(debugStream));
debugStream.forget(wrapperStream);
} else {
objectOutput.forget(wrapperStream);
}
#else
objectOutput.forget(wrapperStream);
#endif
storageStream.forget(stream);
return NS_OK;
}
nsresult NewBufferFromStorageStream(nsIStorageStream* storageStream,
UniqueFreePtr<char[]>* buffer,
uint32_t* len) {
nsresult rv;
nsCOMPtr<nsIInputStream> inputStream;
rv = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
NS_ENSURE_SUCCESS(rv, rv);
uint64_t avail64;
rv = inputStream->Available(&avail64);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(avail64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
uint32_t avail = (uint32_t)avail64;
auto temp = UniqueFreePtr<char[]>(
reinterpret_cast<char*>(malloc(sizeof(char) * avail)));
uint32_t read;
rv = inputStream->Read(temp.get(), avail, &read);
if (NS_SUCCEEDED(rv) && avail != read) rv = NS_ERROR_UNEXPECTED;
if (NS_FAILED(rv)) {
return rv;
}
*len = avail;
*buffer = std::move(temp);
return NS_OK;
}
static const char baseName[2][5] = {"gre/", "app/"};
static inline bool canonicalizeBase(nsAutoCString& spec, nsACString& out,
ResourceType& aResourceType) {
nsAutoCString greBase, appBase;
nsresult rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, greBase);
if (NS_FAILED(rv) || !greBase.Length()) return false;
rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, appBase);
if (NS_FAILED(rv)) return false;
bool underGre = StringBeginsWith(spec, greBase);
bool underApp = appBase.Length() && StringBeginsWith(spec, appBase);
if (!underGre && !underApp) return false;
/**
* At this point, if both underGre and underApp are true, it can be one
* of the two following cases:
* - the GRE directory points to a subdirectory of the APP directory,
* meaning spec points under GRE.
* - the APP directory points to a subdirectory of the GRE directory,
* meaning spec points under APP.
* Checking the GRE and APP path length is enough to know in which case
* we are.
*/
if (underGre && underApp && greBase.Length() < appBase.Length())
underGre = false;
aResourceType = underGre ? ResourceType::Gre : ResourceType::App;
out.AppendLiteral("/resource/");
out.Append(
baseName[underGre ? mozilla::Omnijar::GRE : mozilla::Omnijar::APP]);
out.Append(Substring(spec, underGre ? greBase.Length() : appBase.Length()));
return true;
}
/**
* ResolveURI transforms a chrome: or resource: URI into the URI for its
* underlying resource, or returns any other URI unchanged.
*/
nsresult ResolveURI(nsIURI* in, nsIURI** out) {
nsresult rv;
nsAutoCString scheme;
in->GetScheme(scheme);
// Resolve resource:// URIs. At the end of this if/else block, we
// have both spec and uri variables identifying the same URI.
if (scheme.EqualsLiteral("resource") || scheme.EqualsLiteral("moz-src")) {
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIProtocolHandler> ph;
rv = ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(ph));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISubstitutingProtocolHandler> irph(do_QueryInterface(ph, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString spec;
rv = irph->ResolveURI(in, spec);
NS_ENSURE_SUCCESS(rv, rv);
return ioService->NewURI(spec, nullptr, nullptr, out);
}
if (scheme.EqualsLiteral("chrome")) {
nsCOMPtr<nsIChromeRegistry> chromeReg =
mozilla::services::GetChromeRegistry();
if (!chromeReg) return NS_ERROR_UNEXPECTED;
return chromeReg->ConvertChromeURL(in, out);
}
*out = do_AddRef(in).take();
return NS_OK;
}
static nsresult PathifyURIImpl(nsIURI* in, nsACString& out,
ResourceType& aResourceType) {
nsCOMPtr<nsIURI> uri;
nsresult rv = ResolveURI(in, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString spec;
rv = uri->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
if (!canonicalizeBase(spec, out, aResourceType)) {
if (uri->SchemeIs("file")) {
aResourceType = ResourceType::File;
nsCOMPtr<nsIFileURL> baseFileURL;
baseFileURL = do_QueryInterface(uri, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString path;
rv = baseFileURL->GetPathQueryRef(path);
NS_ENSURE_SUCCESS(rv, rv);
out.Append(path);
} else if (uri->SchemeIs("jar")) {
aResourceType = ResourceType::Xpi;
nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> jarFileURI;
rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI));
NS_ENSURE_SUCCESS(rv, rv);
ResourceType unused;
rv = PathifyURIImpl(jarFileURI, out, unused);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString path;
rv = jarURI->GetJAREntry(path);
NS_ENSURE_SUCCESS(rv, rv);
out.Append('/');
out.Append(path);
} else { // Very unlikely
aResourceType = ResourceType::Other;
rv = uri->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
out.Append('/');
out.Append(spec);
}
}
return NS_OK;
}
nsresult PathifyURI(const char* loaderType, size_t loaderTypeLength, nsIURI* in,
nsACString& out, ResourceType* aResourceType) {
out.AssignASCII(loaderType, loaderTypeLength);
return PathifyURIImpl(in, out, *aResourceType);
}
} // namespace scache
} // namespace mozilla