Bug 1353179 - Fix the content process permission assertion, and add support for pre-load permissions, r=baku

MozReview-Commit-ID: DAVPue8krnH
This commit is contained in:
Michael Layzell 2017-04-04 11:46:26 -04:00
parent 8b7ba021a4
commit 4966d64d16
3 changed files with 186 additions and 56 deletions

View File

@ -83,6 +83,59 @@ LogToConsole(const nsAString& aMsg)
namespace {
// These permissions are special permissions which must be transmitted to the
// content process before documents with their principals have loaded within
// that process. For example, the permissions which are used for content
// blocking are sent using this mechanism.
//
// Permissions which are in this list are considered to have a "" permission
// key, even if their principal would not normally have that key.
static const char* kPreloadPermissions[] = {
// NOTE: These permissions are the different nsContentBlocker permissions for
// allowing or denying certain content types from being loaded. Every
// permission listed in the `kTypeString` array in nsContentBlocker.cpp should
// appear in this list.
"other",
"script",
"image",
"stylesheet",
"object",
"document",
"subdocument",
"refresh",
"xbl",
"ping",
"xmlhttprequest",
"objectsubrequest",
"dtd",
"font",
"media",
"websocket",
"csp_report",
"xslt",
"beacon",
"fetch",
"image",
"manifest"
// ------------------------------------------
};
// NOTE: nullptr can be passed as aType - if it is this function will return
// "false" unconditionally.
bool
IsPreloadPermission(const char* aType)
{
if (aType) {
for (uint32_t i = 0; i < mozilla::ArrayLength(kPreloadPermissions); ++i) {
if (!strcmp(aType, kPreloadPermissions[i])) {
return true;
}
}
}
return false;
}
nsresult
GetOriginFromPrincipal(nsIPrincipal* aPrincipal, nsACString& aOrigin)
{
@ -650,22 +703,6 @@ nsPermissionManager::PermissionKey::CreateFromPrincipal(nsIPrincipal* aPrincipal
return nullptr;
}
#ifdef DEBUG
// Creating a PermissionsKey to look up a permission if we haven't had those
// keys synced down yet is problematic, so we do a check here and crash on
// debug builds if we see it happening.
if (XRE_IsContentProcess()) {
nsAutoCString permissionKey;
GetKeyForPrincipal(aPrincipal, permissionKey);
if (!gPermissionManager->mAvailablePermissionKeys.Contains(permissionKey)) {
NS_WARNING(nsPrintfCString("This content process hasn't received the "
"permissions for %s yet", permissionKey.get()).get());
MOZ_CRASH("The content process hasn't recieved permissions for an origin yet.");
}
}
#endif
return new PermissionKey(origin);
}
@ -1619,7 +1656,7 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal,
aExpireType, aExpireTime);
nsAutoCString permissionKey;
GetKeyForPrincipal(aPrincipal, permissionKey);
GetKeyForPermission(aPrincipal, aType.get(), permissionKey);
nsTArray<ContentParent*> cplist;
ContentParent::GetAll(cplist);
@ -1630,6 +1667,8 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal,
}
}
MOZ_ASSERT(PermissionAvaliable(aPrincipal, aType.get()));
// look up the type index
int32_t typeIndex = GetTypeIndex(aType.get(), true);
NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
@ -2096,6 +2135,8 @@ nsPermissionManager::GetPermissionObject(nsIPrincipal* aPrincipal,
return NS_ERROR_INVALID_ARG;
}
MOZ_ASSERT(PermissionAvaliable(aPrincipal, aType));
int32_t typeIndex = GetTypeIndex(aType, false);
// If type == -1, the type isn't known,
// so just return NS_OK
@ -2173,6 +2214,8 @@ nsPermissionManager::CommonTestPermission(nsIPrincipal* aPrincipal,
return NS_OK;
}
MOZ_ASSERT(PermissionAvaliable(aPrincipal, aType));
int32_t typeIndex = GetTypeIndex(aType, false);
// If type == -1, the type isn't known,
// so just return NS_OK
@ -2204,6 +2247,8 @@ nsPermissionManager::GetPermissionHashKey(nsIPrincipal* aPrincipal,
uint32_t aType,
bool aExactHostMatch)
{
MOZ_ASSERT(PermissionAvaliable(aPrincipal, mTypeArray[aType].get()));
nsresult rv;
RefPtr<PermissionKey> key =
PermissionKey::CreateFromPrincipal(aPrincipal, rv);
@ -2299,6 +2344,8 @@ NS_IMETHODIMP nsPermissionManager::GetAllForURI(nsIURI* aURI, nsISimpleEnumerato
nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(PermissionAvaliable(principal, nullptr));
RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(principal, rv);
if (!key) {
MOZ_ASSERT(NS_FAILED(rv));
@ -2912,6 +2959,8 @@ nsPermissionManager::UpdateExpireTime(nsIPrincipal* aPrincipal,
return NS_ERROR_INVALID_ARG;
}
MOZ_ASSERT(PermissionAvaliable(aPrincipal, aType));
int32_t typeIndex = GetTypeIndex(aType, false);
// If type == -1, the type isn't known,
// so just return NS_OK
@ -2959,15 +3008,6 @@ nsPermissionManager::GetPermissionsWithKey(const nsACString& aPermissionKey,
continue;
}
// Get the permission key and make sure that it matches the aPermissionKey
// passed in.
nsAutoCString permissionKey;
GetKeyForPrincipal(principal, permissionKey);
if (permissionKey != aPermissionKey) {
continue;
}
for (const auto& permEntry : entry->GetPermissions()) {
// Given how "default" permissions work and the possibility of them being
// overridden with UNKNOWN_ACTION, we might see this value here - but we
@ -2976,11 +3016,20 @@ nsPermissionManager::GetPermissionsWithKey(const nsACString& aPermissionKey,
continue;
}
aPerms.AppendElement(IPC::Permission(entry->GetKey()->mOrigin,
mTypeArray.ElementAt(permEntry.mType),
permEntry.mPermission,
permEntry.mExpireType,
permEntry.mExpireTime));
// XXX: This performs extra work, such as in many cases re-computing the
// Origin (which we just computed the nsIPrincipal from). We may want to
// implement a custom version of this logic which avoids that extra work.
// See bug 1354700.
nsAutoCString permissionKey;
GetKeyForPermission(principal, mTypeArray[permEntry.mType].get(), permissionKey);
if (permissionKey == aPermissionKey) {
aPerms.AppendElement(IPC::Permission(entry->GetKey()->mOrigin,
mTypeArray.ElementAt(permEntry.mType),
permEntry.mPermission,
permEntry.mExpireType,
permEntry.mExpireTime));
}
}
}
@ -3013,7 +3062,7 @@ nsPermissionManager::SetPermissionsWithKey(const nsACString& aPermissionKey,
#ifdef DEBUG
nsAutoCString permissionKey;
GetKeyForPrincipal(principal, permissionKey);
GetKeyForPermission(principal, perm.type.get(), permissionKey);
MOZ_ASSERT(permissionKey == aPermissionKey,
"The permission keys which were sent over should match!");
#endif
@ -3069,6 +3118,18 @@ nsPermissionManager::GetKeyForPrincipal(nsIPrincipal* aPrincipal, nsACString& aK
return;
}
/* static */ void
nsPermissionManager::GetKeyForPermission(nsIPrincipal* aPrincipal, const char* aType, nsACString& aKey)
{
// Preload permissions have the "" key.
if (IsPreloadPermission(aType)) {
aKey.Truncate();
return;
}
GetKeyForPrincipal(aPrincipal, aKey);
}
/* static */ nsTArray<nsCString>
nsPermissionManager::GetAllKeysForPrincipal(nsIPrincipal* aPrincipal)
{
@ -3102,3 +3163,21 @@ nsPermissionManager::BroadcastPermissionsForPrincipalToAllContentProcesses(nsIPr
return NS_OK;
}
bool
nsPermissionManager::PermissionAvaliable(nsIPrincipal* aPrincipal, const char* aType)
{
if (XRE_IsContentProcess()) {
nsAutoCString permissionKey;
// NOTE: GetKeyForPermission accepts a null aType.
GetKeyForPermission(aPrincipal, aType, permissionKey);
if (!mAvailablePermissionKeys.Contains(permissionKey)) {
// Emit a useful diagnostic warning with the permissionKey for the process
// which hasn't received permissions yet.
NS_WARNING(nsPrintfCString("This content process hasn't received the "
"permissions for %s yet", permissionKey.get()).get());
return false;
}
}
return true;
}

View File

@ -223,6 +223,27 @@ public:
*/
static void GetKeyForPrincipal(nsIPrincipal* aPrincipal, nsACString& aPermissionKey);
/**
* See `nsIPermissionManager::GetPermissionsWithKey` for more info on
* permission keys.
*
* Get the permission key corresponding to the given Principal and type. This
* method is intentionally infallible, as we want to provide an permission key
* to every principal. Principals which don't have meaningful URIs with
* http://, https://, or ftp:// schemes are given the default "" Permission
* Key.
*
* This method is different from GetKeyForPrincipal in that it also takes
* permissions which must be sent down before loading a document into account.
*
* @param aPrincipal The Principal which the key is to be extracted from.
* @param aType The type of the permission to get the key for.
* @param aPermissionKey A string which will be filled with the permission key.
*/
static void GetKeyForPermission(nsIPrincipal* aPrincipal,
const char* aType,
nsACString& aPermissionKey);
/**
* See `nsIPermissionManager::GetPermissionsWithKey` for more info on
* permission keys.
@ -293,6 +314,15 @@ private:
nsresult
RemoveAllModifiedSince(int64_t aModificationTime);
/**
* Returns false if this permission manager wouldn't have the permission
* requested avaliable.
*
* If aType is nullptr, checks that the permission manager would have all
* permissions avaliable for the given principal.
*/
bool PermissionAvaliable(nsIPrincipal* aPrincipal, const char* aType);
nsCOMPtr<mozIStorageConnection> mDBConn;
nsCOMPtr<mozIStorageAsyncStatement> mStmtInsert;
nsCOMPtr<mozIStorageAsyncStatement> mStmtDelete;

View File

@ -37,22 +37,27 @@ add_task(function* () {
addPerm("http://foo.bar.example.com", "perm2");
addPerm("about:home", "perm3");
addPerm("https://example.com", "perm4");
// NOTE: This permission is a preload permission, so it should be avaliable in the content process from startup.
addPerm("https://somerandomwebsite.com", "document");
yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (aBrowser) {
yield ContentTask.spawn(aBrowser, null, function* () {
// Before the load http URIs shouldn't have been sent down yet
is(Services.perms.testPermission(Services.io.newURI("http://example.com"),
"perm1"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "perm1-1");
is(Services.perms.testPermission(Services.io.newURI("http://foo.bar.example.com"),
"perm2"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "perm2-1");
is(Services.perms.testPermission(Services.io.newURI("about:home"),
"perm3"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "perm3-1");
is(Services.perms.testPermission(Services.io.newURI("https://example.com"),
"perm4"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "perm4-1");
is(Services.perms.testPermission(Services.io.newURI("https://somerandomwebsite.com"),
"document"),
Services.perms.ALLOW_ACTION, "document-1");
// Perform a load of example.com
yield new Promise(resolve => {
@ -65,50 +70,60 @@ add_task(function* () {
// After the load finishes, we should know about example.com, but not foo.bar.example.com
is(Services.perms.testPermission(Services.io.newURI("http://example.com"),
"perm1"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "perm1-2");
is(Services.perms.testPermission(Services.io.newURI("http://foo.bar.example.com"),
"perm2"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "perm2-2");
is(Services.perms.testPermission(Services.io.newURI("about:home"),
"perm3"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "perm3-2");
is(Services.perms.testPermission(Services.io.newURI("https://example.com"),
"perm4"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "perm4-2");
is(Services.perms.testPermission(Services.io.newURI("https://somerandomwebsite.com"),
"document"),
Services.perms.ALLOW_ACTION, "document-2");
});
addPerm("http://example.com", "newperm1");
addPerm("http://foo.bar.example.com", "newperm2");
addPerm("about:home", "newperm3");
addPerm("https://example.com", "newperm4");
addPerm("https://someotherrandomwebsite.com", "document");
yield ContentTask.spawn(aBrowser, null, function* () {
// The new permissions should be avaliable, but only for
// http://example.com, and about:home
is(Services.perms.testPermission(Services.io.newURI("http://example.com"),
"perm1"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "perm1-3");
is(Services.perms.testPermission(Services.io.newURI("http://example.com"),
"newperm1"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "newperm1-3");
is(Services.perms.testPermission(Services.io.newURI("http://foo.bar.example.com"),
"perm2"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "perm2-3");
is(Services.perms.testPermission(Services.io.newURI("http://foo.bar.example.com"),
"newperm2"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "newperm2-3");
is(Services.perms.testPermission(Services.io.newURI("about:home"),
"perm3"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "perm3-3");
is(Services.perms.testPermission(Services.io.newURI("about:home"),
"newperm3"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "newperm3-3");
is(Services.perms.testPermission(Services.io.newURI("https://example.com"),
"perm4"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "perm4-3");
is(Services.perms.testPermission(Services.io.newURI("https://example.com"),
"newperm4"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "newperm4-3");
is(Services.perms.testPermission(Services.io.newURI("https://somerandomwebsite.com"),
"document"),
Services.perms.ALLOW_ACTION, "document-3");
is(Services.perms.testPermission(Services.io.newURI("https://someotherrandomwebsite.com"),
"document"),
Services.perms.ALLOW_ACTION, "otherdocument-3");
// Loading a subdomain now, on https
yield new Promise(resolve => {
@ -122,28 +137,34 @@ add_task(function* () {
// permissions are also avaliable for its parent domain, https://example.com!
is(Services.perms.testPermission(Services.io.newURI("http://example.com"),
"perm1"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "perm1-4");
is(Services.perms.testPermission(Services.io.newURI("http://example.com"),
"newperm1"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "newperm1-4");
is(Services.perms.testPermission(Services.io.newURI("http://foo.bar.example.com"),
"perm2"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "perm2-4");
is(Services.perms.testPermission(Services.io.newURI("http://foo.bar.example.com"),
"newperm2"),
Services.perms.UNKNOWN_ACTION);
Services.perms.UNKNOWN_ACTION, "newperm2-4");
is(Services.perms.testPermission(Services.io.newURI("about:home"),
"perm3"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "perm3-4");
is(Services.perms.testPermission(Services.io.newURI("about:home"),
"newperm3"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "newperm3-4");
is(Services.perms.testPermission(Services.io.newURI("https://example.com"),
"perm4"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "perm4-4");
is(Services.perms.testPermission(Services.io.newURI("https://example.com"),
"newperm4"),
Services.perms.ALLOW_ACTION);
Services.perms.ALLOW_ACTION, "newperm4-4");
is(Services.perms.testPermission(Services.io.newURI("https://somerandomwebsite.com"),
"document"),
Services.perms.ALLOW_ACTION, "document-4");
is(Services.perms.testPermission(Services.io.newURI("https://someotherrandomwebsite.com"),
"document"),
Services.perms.ALLOW_ACTION, "otherdocument-4");
});
});
});