From 55b95d81deaec14d16e892f4c46ccf7f5ff68336 Mon Sep 17 00:00:00 2001 From: Paul Zuehlcke Date: Tue, 19 Jan 2021 10:04:05 +0000 Subject: [PATCH] Bug 1685355 - Use storage principal when clearing data via clear-site-data header. r=johannh Differential Revision: https://phabricator.services.mozilla.com/D101192 --- .../antitracking/test/browser/browser.ini | 3 + .../browser_partitionedClearSiteDataHeader.js | 575 ++++++++++++++++++ .../test/browser/clearSiteData.sjs | 6 + .../cleardata/SiteDataTestUtils.jsm | 54 +- .../clearsitedata/ClearSiteData.cpp | 5 +- 5 files changed, 627 insertions(+), 16 deletions(-) create mode 100644 toolkit/components/antitracking/test/browser/browser_partitionedClearSiteDataHeader.js create mode 100644 toolkit/components/antitracking/test/browser/clearSiteData.sjs diff --git a/toolkit/components/antitracking/test/browser/browser.ini b/toolkit/components/antitracking/test/browser/browser.ini index cc6099c16cb9..6daf1f4737d1 100644 --- a/toolkit/components/antitracking/test/browser/browser.ini +++ b/toolkit/components/antitracking/test/browser/browser.ini @@ -167,3 +167,6 @@ support-files = file_saveAsVideo.sjs file_saveAsPageInfo.html file_video.ogv +[browser_partitionedClearSiteDataHeader.js] +support-files = + clearSiteData.sjs diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedClearSiteDataHeader.js b/toolkit/components/antitracking/test/browser/browser_partitionedClearSiteDataHeader.js new file mode 100644 index 000000000000..a23a4bd38d92 --- /dev/null +++ b/toolkit/components/antitracking/test/browser/browser_partitionedClearSiteDataHeader.js @@ -0,0 +1,575 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use-strict"; + +/** + * Tests that when receiving the "clear-site-data" header - with dFPI enabled - + * we clear storage under the correct partition. + */ + +const { SiteDataTestUtils } = ChromeUtils.import( + "resource://testing-common/SiteDataTestUtils.jsm" +); + +const HOST_A = "example.com"; +const HOST_B = "example.org"; +const ORIGIN_A = `https://${HOST_A}`; +const ORIGIN_B = `https://${HOST_B}`; +const CLEAR_SITE_DATA_PATH = `/${TEST_PATH}clearSiteData.sjs`; +const CLEAR_SITE_DATA_URL_ORIGIN_B = ORIGIN_B + CLEAR_SITE_DATA_PATH; +const CLEAR_SITE_DATA_URL_ORIGIN_A = ORIGIN_A + CLEAR_SITE_DATA_PATH; +const THIRD_PARTY_FRAME_ID_ORIGIN_B = "thirdPartyFrame"; +const THIRD_PARTY_FRAME_ID_ORIGIN_A = "thirdPartyFrame2"; +const STORAGE_KEY = "testKey"; + +/** + * Creates an iframe in the passed browser and waits for it to load. + * @param {Browser} browser - Browser to create the frame in. + * @param {String} src - Frame source url. + * @param {String} id - Frame id. + * @param {boolean} sandbox - Whether the frame should be sandboxed. + * @returns {Promise} - Resolves once the frame has loaded. + */ +function createFrame(browser, src, id, sandbox) { + return SpecialPowers.spawn( + browser, + [{ page: src, frameId: id, sandbox }], + async function(obj) { + await new content.Promise(resolve => { + let frame = content.document.createElement("iframe"); + if (obj.sandbox) { + frame.setAttribute("sandbox", "allow-scripts"); + } + frame.src = obj.page; + frame.id = obj.frameId; + frame.addEventListener("load", resolve, { once: true }); + content.document.body.appendChild(frame); + }); + } + ); +} + +/** + * Creates a new tab, loads a url and creates an iframe. + * Callers need to clean up the tab before the test ends. + * @param {String} firstPartyUrl - Url to load in tab. + * @param {String} thirdPartyUrl - Url to load in frame. + * @param {String} frameId - Id of iframe element. + * @param {boolean} sandbox - Whether the frame should be sandboxed. + * @returns {Promise} - Resolves with the tab and the frame BrowsingContext once + * the tab and the frame have loaded. + */ +async function createTabWithFrame( + firstPartyUrl, + thirdPartyUrl, + frameId, + sandbox +) { + // Create tab and wait for it to be loaded. + let tab = BrowserTestUtils.addTab(gBrowser, firstPartyUrl); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + // Create cross origin iframe. + await createFrame(tab.linkedBrowser, thirdPartyUrl, frameId, sandbox); + + // Return BrowsingContext of created iframe. + return { tab, frameBC: tab.linkedBrowser.browsingContext.children[0] }; +} + +/** + * Test wrapper for the ClearSiteData tests. + * Loads ORIGIN_A and ORIGIN_B in two tabs and inserts a cross origin pointing + * to the other iframe each. + * Both frames ORIGIN_B under ORIGIN_A and ORIGIN_A under ORIGIN_B will be + * storage partitioned. + * Depending on the clearDataContext variable we then either navigate ORIGIN_A + * (as top level) or ORIGIN_B (as third party frame) to the clear-site-data + * endpoint. + * @param {function} cbPreClear - Called after initial setup, once top levels + * and frames have been loaded. + * @param {function} cbPostClear - Called after data has been cleared via the + * "Clear-Site-Data" header. + * @param {("firstParty"|"thirdPartyPartitioned")} clearDataContext - Whether to + * navigate to the path that sets the "Clear-Site-Data" header with the first or + * third party. + * @param {boolean} [sandboxFrame] - Whether the frames should be sandboxed. No + * sandbox by default. + */ +async function runClearSiteDataTest( + cbPreClear, + cbPostClear, + clearDataContext, + sandboxFrame = false +) { + // Create a tabs for origin A and B with cross origins frames B and A + let [ + { frameBC: frameContextB, tab: tabA }, + { frameBC: frameContextA, tab: tabB }, + ] = await Promise.all([ + createTabWithFrame( + ORIGIN_A, + ORIGIN_B, + THIRD_PARTY_FRAME_ID_ORIGIN_B, + sandboxFrame + ), + createTabWithFrame( + ORIGIN_B, + ORIGIN_A, + THIRD_PARTY_FRAME_ID_ORIGIN_A, + sandboxFrame + ), + ]); + + let browserA = tabA.linkedBrowser; + let contextA = browserA.browsingContext; + let browserB = tabB.linkedBrowser; + let contextB = browserB.browsingContext; + + // Run test callback before clear-site-data + if (cbPreClear) { + await cbPreClear(contextA, contextB, frameContextB, frameContextA); + } + + // Navigate to path with clear-site-data header + // Depending on the clearDataContext variable we either do this with the + // top browser or the third party storage partitioned frame (B embedded in A). + info(`Opening path with clear-site-data-header for ${clearDataContext}`); + if (clearDataContext == "firstParty") { + // Open in new tab so we keep our current test tab intact. The + // post-clear-callback might need it. + await BrowserTestUtils.withNewTab( + (browserA, CLEAR_SITE_DATA_URL_ORIGIN_A), + () => {} + ); + } else if (clearDataContext == "thirdPartyPartitioned") { + // Navigate frame to path with clear-site-data header + await SpecialPowers.spawn( + browserA, + [ + { + page: CLEAR_SITE_DATA_URL_ORIGIN_B, + frameId: THIRD_PARTY_FRAME_ID_ORIGIN_B, + }, + ], + async function(obj) { + await new content.Promise(resolve => { + let frame = content.document.getElementById(obj.frameId); + frame.addEventListener("load", resolve, { once: true }); + frame.src = obj.page; + }); + } + ); + } else { + ok(false, "Invalid context requested for clear-site-data"); + } + + if (cbPostClear) { + await cbPostClear(contextA, contextB, frameContextB, frameContextA); + } + + info("Cleaning up."); + BrowserTestUtils.removeTab(tabA); + BrowserTestUtils.removeTab(tabB); + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve); + }); +} + +/** + * Create an origin with partitionKey. + * @param {String} originNoSuffix - Origin without origin attributes. + * @param {String} [firstParty] - First party to create partitionKey. + * @returns {String} Origin with suffix. If not passed this will return the + * umodified origin. + */ +function getOrigin(originNoSuffix, firstParty) { + let origin = originNoSuffix; + if (firstParty) { + let [scheme, host] = firstParty.split("://"); + origin += `^partitionKey=(${scheme},${host})`; + } + return origin; +} + +/** + * Sets a storage item for an origin. + * @param {("cookie"|"localStorage")} storageType - Which storage type to use. + * @param {String} originNoSuffix - Context to set storage item in. + * @param {String} [firstParty] - Optional first party domain to partition + * under. + * @param {String} key - Key of the entry. + * @param {String} value - Value of the entry. + */ +function setStorageEntry(storageType, originNoSuffix, firstParty, key, value) { + if (storageType != "cookie" && storageType != "localStorage") { + ok(false, "Invalid storageType passed"); + return; + } + + let origin = getOrigin(originNoSuffix, firstParty); + + if (storageType == "cookie") { + SiteDataTestUtils.addToCookies(origin, key, value); + return; + } + // localStorage + SiteDataTestUtils.addToLocalStorage(origin, key, value); +} + +/** + * Tests whether a host sets a cookie. + * For the purpose of this test we assume that there is either one or no cookie + * set. + * This performs cookie lookups directly via the cookie service. + * @param {boolean} hasCookie - Whether we expect to see a cookie. + * @param {String} originNoSuffix - Origin the cookie is stored for. + * @param {String|null} firstParty - Whether to test for a partitioned cookie. + * If set this will be used to construct the partitionKey. + * @param {String} [key] - Expected key / name of the cookie. + * @param {String} [value] - Expected value of the cookie. + */ +function testHasCookie(hasCookie, originNoSuffix, firstParty, key, value) { + let origin = getOrigin(originNoSuffix, firstParty); + + let label = `${originNoSuffix}${ + firstParty ? ` (partitioned under ${firstParty})` : "" + }`; + + if (!hasCookie) { + ok( + !SiteDataTestUtils.hasCookies(origin), + `Cookie for ${label} is not set for key ${key}` + ); + return; + } + + ok( + SiteDataTestUtils.hasCookies(origin, [{ key, value }]), + `Cookie for ${label} is set ${key}=${value}` + ); +} + +/** + * Tests whether a context has a localStorage entry. + * @param {boolean} hasEntry - Whether we expect to see an entry. + * @param {String} originNoSuffix - Origin to test localStorage for. + * @param {String} [firstParty] - First party context to test under. + * @param {String} key - key of the localStorage item. + * @param {String} [expectedValue] - Expected value of the item. + */ +function testHasLocalStorageEntry( + hasEntry, + originNoSuffix, + firstParty, + key, + expectedValue +) { + if (key == null) { + ok(false, "localStorage key is mandatory"); + return; + } + let label = `${originNoSuffix}${ + firstParty ? ` (partitioned under ${firstParty})` : "" + }`; + let origin = getOrigin(originNoSuffix, firstParty); + if (hasEntry) { + let hasEntry = SiteDataTestUtils.hasLocalStorage(origin, [ + { key, value: expectedValue }, + ]); + ok( + hasEntry, + `localStorage for ${label} has expected value ${key}=${expectedValue}` + ); + } else { + let hasEntry = SiteDataTestUtils.hasLocalStorage(origin); + ok(!hasEntry, `localStorage for ${label} is not set for key ${key}`); + } +} + +/** + * Sets the initial storage entries used by the storage tests in this file. + * 1. first party ( A ) + * 2. first party ( B ) + * 3. third party partitioned ( B under A) + * 4. third party partitioned ( A under B) + * The entry values reflect which context they are set for. + * @param {("cookie"|"localStorage")} storageType - Storage type to initialize. + */ +async function setupInitialStorageState(storageType) { + if (storageType != "cookie" && storageType != "localStorage") { + ok(false, "Invalid storageType passed"); + return; + } + + // Set a first party entry + setStorageEntry(storageType, ORIGIN_A, null, STORAGE_KEY, "firstParty"); + + // Set a storage entry in the storage partitioned third party frame + setStorageEntry( + storageType, + ORIGIN_B, + ORIGIN_A, + STORAGE_KEY, + "thirdPartyPartitioned" + ); + + // Set a storage entry in the non storage partitioned third party page + setStorageEntry(storageType, ORIGIN_B, null, STORAGE_KEY, "thirdParty"); + + // Set a storage entry in the second storage partitioned third party frame. + setStorageEntry( + storageType, + ORIGIN_A, + ORIGIN_B, + STORAGE_KEY, + "thirdPartyPartitioned2" + ); + + info("Test that storage entries are set for all contexts"); + + if (storageType == "cookie") { + testHasCookie(true, ORIGIN_A, null, STORAGE_KEY, "firstParty"); + testHasCookie(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty"); + testHasCookie( + true, + ORIGIN_B, + ORIGIN_A, + STORAGE_KEY, + "thirdPartyPartitioned" + ); + testHasCookie( + true, + ORIGIN_A, + ORIGIN_B, + STORAGE_KEY, + "thirdPartyPartitioned2" + ); + return; + } + + testHasLocalStorageEntry(true, ORIGIN_A, null, STORAGE_KEY, "firstParty"); + testHasLocalStorageEntry(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty"); + testHasLocalStorageEntry( + true, + ORIGIN_B, + ORIGIN_A, + STORAGE_KEY, + "thirdPartyPartitioned" + ); + testHasLocalStorageEntry( + true, + ORIGIN_A, + ORIGIN_B, + STORAGE_KEY, + "thirdPartyPartitioned2" + ); +} + +add_task(async function setup() { + info("Starting ClearSiteData test"); + + await SpecialPowers.flushPrefEnv(); + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.storage_access.enabled", true], + [ + "network.cookie.cookieBehavior", + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, + ], + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.pbmode.enabled", false], + // Needed for SiteDataTestUtils#hasLocalStorage + ["dom.storage.client_validation", false], + ], + }); +}); + +/** + * Test clearing partitioned cookies via clear-site-data header + * (Cleared via the cookie service). + */ + +/** + * Tests that when a storage partitioned third party frame loads a site with + * "Clear-Site-Data", the cookies are cleared for only that partitioned frame. + */ +add_task(async function cookieClearThirdParty() { + await runClearSiteDataTest( + // Pre Clear-Site-Data + () => setupInitialStorageState("cookie"), + // Post Clear-Site-Data + () => { + info("Testing: First party cookie has not changed"); + testHasCookie(true, ORIGIN_A, null, STORAGE_KEY, "firstParty"); + info("Testing: Unpartitioned cookie has not changed"); + testHasCookie(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty"); + info("Testing: Partitioned cookie for HOST_B (HOST_A) has been cleared"); + testHasCookie(false, ORIGIN_B, ORIGIN_A); + info("Testing: Partitioned cookie for HOST_A (HOST_B) has not changed"); + testHasCookie( + true, + ORIGIN_A, + ORIGIN_B, + STORAGE_KEY, + "thirdPartyPartitioned2" + ); + }, + // Send clear-site-data header in partitioned third party context. + "thirdPartyPartitioned" + ); +}); + +/** + * Tests that when a sandboxed storage partitioned third party frame loads a + * site with "Clear-Site-Data", no cookies are cleared and we don't crash. + * Crash details in Bug 1686938. + */ +add_task(async function cookieClearThirdPartySandbox() { + await runClearSiteDataTest( + // Pre Clear-Site-Data + () => setupInitialStorageState("cookie"), + // Post Clear-Site-Data + () => { + info("Testing: First party cookie has not changed"); + testHasCookie(true, ORIGIN_A, null, STORAGE_KEY, "firstParty"); + info("Testing: Unpartitioned cookie has not changed"); + testHasCookie(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty"); + info("Testing: Partitioned cookie for HOST_B (HOST_A) has not changed"); + testHasCookie( + true, + ORIGIN_B, + ORIGIN_A, + STORAGE_KEY, + "thirdPartyPartitioned" + ); + info("Testing: Partitioned cookie for HOST_A (HOST_B) has not changed"); + testHasCookie( + true, + ORIGIN_A, + ORIGIN_B, + STORAGE_KEY, + "thirdPartyPartitioned2" + ); + }, + // Send clear-site-data header in partitioned third party context. + "thirdPartyPartitioned", + true + ); +}); + +/** + * Tests that when the we load a path with "Clear-Site-Data" at top level, the + * cookies are cleared only in the first party context. + */ +add_task(async function cookieClearFirstParty() { + await runClearSiteDataTest( + // Pre Clear-Site-Data + () => setupInitialStorageState("cookie"), + // Post Clear-Site-Data + () => { + info("Testing: First party cookie has been cleared"); + testHasCookie(false, ORIGIN_A, null); + info("Testing: Unpartitioned cookie has not changed"); + testHasCookie(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty"); + info("Testing: Partitioned cookie for HOST_B (HOST_A) has not changed"); + testHasCookie( + true, + ORIGIN_B, + ORIGIN_A, + STORAGE_KEY, + "thirdPartyPartitioned" + ); + info("Testing: Partitioned cookie for HOST_A (HOST_B) has not changed"); + testHasCookie( + true, + ORIGIN_A, + ORIGIN_B, + STORAGE_KEY, + "thirdPartyPartitioned2" + ); + }, + // Send clear-site-data header in first party context. + "firstParty" + ); +}); + +/** + * Test clearing partitioned localStorage via clear-site-data header + * (Cleared via the quota manager). + */ + +/** + * Tests that when a storage partitioned third party frame loads a site with + * "Clear-Site-Data", localStorage is cleared for only that partitioned frame. + */ +add_task(async function localStorageClearThirdParty() { + await runClearSiteDataTest( + // Pre Clear-Site-Data + () => setupInitialStorageState("localStorage"), + // Post Clear-Site-Data + async () => { + info("Testing: First party localStorage has not changed"); + testHasLocalStorageEntry(true, ORIGIN_A, null, STORAGE_KEY, "firstParty"); + info("Testing: Unpartitioned localStorage has not changed"); + testHasLocalStorageEntry(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty"); + info( + "Testing: Partitioned localStorage for HOST_B (HOST_A) has been cleared" + ); + testHasLocalStorageEntry(false, ORIGIN_B, ORIGIN_A, STORAGE_KEY); + info( + "Testing: Partitioned localStorage for HOST_A (HOST_B) has not changed" + ); + testHasLocalStorageEntry( + true, + ORIGIN_A, + ORIGIN_B, + STORAGE_KEY, + "thirdPartyPartitioned2" + ); + }, + // Send clear-site-data header in partitioned third party context. + "thirdPartyPartitioned" + ); +}); + +/** + * Tests that when the we load a path with "Clear-Site-Data" at top level, + * localStorage is cleared only in the first party context. + */ +add_task(async function localStorageClearFirstParty() { + await runClearSiteDataTest( + // Pre Clear-Site-Data + () => setupInitialStorageState("localStorage"), + // Post Clear-Site-Data + () => { + info("Testing: First party localStorage has been cleared"); + testHasLocalStorageEntry(false, ORIGIN_A, null, STORAGE_KEY); + info("Testing: Unpartitioned thirdParty localStorage has not changed"); + testHasLocalStorageEntry(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty"); + info( + "Testing: Partitioned localStorage for HOST_B (HOST_A) has not changed" + ); + testHasLocalStorageEntry( + true, + ORIGIN_B, + ORIGIN_A, + STORAGE_KEY, + "thirdPartyPartitioned" + ); + info( + "Testing: Partitioned localStorage for HOST_A (HOST_B) has not changed" + ); + testHasLocalStorageEntry( + true, + ORIGIN_A, + ORIGIN_B, + STORAGE_KEY, + "thirdPartyPartitioned2" + ); + }, + // Send clear-site-data header in first party context. + "firstParty" + ); +}); diff --git a/toolkit/components/antitracking/test/browser/clearSiteData.sjs b/toolkit/components/antitracking/test/browser/clearSiteData.sjs new file mode 100644 index 000000000000..374f03a474f4 --- /dev/null +++ b/toolkit/components/antitracking/test/browser/clearSiteData.sjs @@ -0,0 +1,6 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + aResponse.setHeader("Clear-Site-Data", '"*"'); + aResponse.setHeader("Content-Type", "text/plain"); + aResponse.write("Clear-Site-Data"); +} diff --git a/toolkit/components/cleardata/SiteDataTestUtils.jsm b/toolkit/components/cleardata/SiteDataTestUtils.jsm index 8a5f60d34ac9..f971e8d5c37e 100644 --- a/toolkit/components/cleardata/SiteDataTestUtils.jsm +++ b/toolkit/components/cleardata/SiteDataTestUtils.jsm @@ -109,8 +109,8 @@ var SiteDataTestUtils = { * Adds a new localStorage entry for the specified origin, with the specified contents. * * @param {String} origin - the origin of the site to add test data for - * @param {String} key [optional] - the localStorage key - * @param {String} value [optional] - the localStorage value + * @param {String} [key] - the localStorage key + * @param {String} [value] - the localStorage value */ addToLocalStorage(origin, key = "foo", value = "bar") { let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin( @@ -122,17 +122,19 @@ var SiteDataTestUtils = { principal, "" ); - storage.setItem("key", "value"); + storage.setItem(key, value); }, /** * Checks whether the given origin is storing data in localStorage * * @param {String} origin - the origin of the site to check + * @param {{key: String, value: String}[]} [testEntries] - An array of entries + * to test for. * * @returns {Boolean} whether the origin has localStorage data */ - hasLocalStorage(origin) { + hasLocalStorage(origin, testEntries) { let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin( origin ); @@ -142,7 +144,16 @@ var SiteDataTestUtils = { principal, "" ); - return !!storage.length; + if (!storage.length) { + return false; + } + if (!testEntries) { + return true; + } + return ( + storage.length >= testEntries.length && + testEntries.every(({ key, value }) => storage.getItem(key) == value) + ); }, /** @@ -180,22 +191,37 @@ var SiteDataTestUtils = { }); }, - hasCookies(origin) { + hasCookies(origin, testEntries) { let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin( origin ); - for (let cookie of Services.cookies.cookies) { - if ( + + let filterFn = cookie => { + return ( ChromeUtils.isOriginAttributesEqual( principal.originAttributes, cookie.originAttributes - ) && - cookie.host.includes(principal.host) - ) { - return true; - } + ) && cookie.host.includes(principal.host) + ); + }; + + // Return on first cookie found for principal. + if (!testEntries) { + return Services.cookies.cookies.some(filterFn); } - return false; + + // Collect all cookies that match the principal + let cookies = Services.cookies.cookies.filter(filterFn); + + if (cookies.length < testEntries.length) { + return false; + } + + // This code isn't very efficient. It should only be used for testing + // a small amount of cookies. + return testEntries.every(({ key, value }) => + cookies.some(cookie => cookie.name == key && cookie.value == value) + ); }, hasIndexedDB(origin) { diff --git a/toolkit/components/clearsitedata/ClearSiteData.cpp b/toolkit/components/clearsitedata/ClearSiteData.cpp index 212d73bebcf8..fc120a16f799 100644 --- a/toolkit/components/clearsitedata/ClearSiteData.cpp +++ b/toolkit/components/clearsitedata/ClearSiteData.cpp @@ -158,8 +158,9 @@ void ClearSiteData::ClearDataFromChannel(nsIHttpChannel* aChannel) { } nsCOMPtr principal; - rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(principal)); - if (NS_WARN_IF(NS_FAILED(rv))) { + rv = ssm->GetChannelResultStoragePrincipal(aChannel, + getter_AddRefs(principal)); + if (NS_WARN_IF(NS_FAILED(rv) || !principal)) { return; }