tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

storage-access-beyond-cookies-iframe-iframe.html (18468B)


      1 <!doctype html>
      2 <meta charset="utf-8">
      3 <script src="/resources/testdriver.js"></script>
      4 <script src="/resources/testdriver-vendor.js"></script>
      5 <script src="/storage-access-api/helpers.js"></script>
      6 <script>
      7 (async function() {
      8  test_driver.set_test_context(window.top);
      9  const type = (new URLSearchParams(window.location.search)).get("type");
     10  const id = (new URLSearchParams(window.location.search)).get("id");
     11  let message = "HasAccess for " + type;
     12  // Step 6 (storage-access-api/storage-access-beyond-cookies.{}.sub.https.html)
     13  try {
     14    await test_driver.set_permission({ name: 'storage-access' }, 'granted');
     15    switch (type) {
     16      case "none": {
     17        let couldRequestStorageAccessForNone = true;
     18        try {
     19          await test_driver.bless("fake user interaction", () => document.requestStorageAccess({}));
     20        } catch (_) {
     21          couldRequestStorageAccessForNone = false;
     22        }
     23        if (couldRequestStorageAccessForNone) {
     24          message = "Requesting access for {} should fail."
     25        }
     26        let couldRequestStorageAccessForAllFalse = true;
     27        try {
     28          await test_driver.bless("fake user interaction", () => document.requestStorageAccess({all:false}));
     29        } catch (_) {
     30          couldRequestStorageAccessForAllFalse = false;
     31        }
     32        if (couldRequestStorageAccessForAllFalse) {
     33          message = "Requesting access for {all:false} should fail."
     34        }
     35        break;
     36      }
     37      case "cookies": {
     38        await test_driver.bless("fake user interaction", () => document.requestStorageAccess({cookies: true}));
     39        if (!(await document.hasUnpartitionedCookieAccess()) || !document.cookie.includes("test="+id)) {
     40          message = "First-party cookies should be readable if cookies were requested.";
     41        }
     42        break;
     43      }
     44      case "sessionStorage": {
     45        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({sessionStorage: true}));
     46        if (id != handle.sessionStorage.getItem("test")) {
     47          message = "No first-party Session Storage access";
     48        }
     49        if (!!window.sessionStorage.getItem("test")) {
     50          message = "Handle should not override window Session Storage";
     51        }
     52        window.onstorage = (e) => {
     53          if (e.key == "window_event") {
     54            if (e.newValue != id) {
     55              handle.sessionStorage.setItem("handle_event", "wrong data " + e.newValue);
     56            } else if (e.storageArea != handle.sessionStorage) {
     57              handle.sessionStorage.setItem("handle_event", "storage area mismatch");
     58            } else {
     59              handle.sessionStorage.setItem("handle_event", id);
     60            }
     61          }
     62        };
     63        break;
     64      }
     65      case "localStorage": {
     66        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({localStorage: true}));
     67        if (id != handle.localStorage.getItem("test")) {
     68          message = "No first-party Local Storage access";
     69        }
     70        if (!!window.localStorage.getItem("test")) {
     71          message = "Handle should not override window Local Storage";
     72        }
     73        window.onstorage = (e) => {
     74          if (e.key == "window_event") {
     75            if (e.newValue != id) {
     76              handle.localStorage.setItem("handle_event", "wrong data " + e.newValue);
     77            } else if (e.storageArea != handle.localStorage) {
     78              handle.localStorage.setItem("handle_event", "storage area mismatch");
     79            } else {
     80              handle.localStorage.setItem("handle_event", id);
     81            }
     82          }
     83        };
     84        break;
     85      }
     86      case "indexedDB": {
     87        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({indexedDB: true}));
     88        const handle_dbs = await handle.indexedDB.databases();
     89        if (handle_dbs.length != 1 || handle_dbs[0].name != id) {
     90          message = "No first-party IndexedDB access";
     91        }
     92        const local_dbs = await window.indexedDB.databases();
     93        if (local_dbs.length != 0) {
     94          message = "Handle should not override window IndexedDB";
     95        }
     96        await handle.indexedDB.deleteDatabase(id);
     97        break;
     98      }
     99      case "locks": {
    100        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({locks: true}));
    101        const handle_state = await handle.locks.query();
    102        if (handle_state.held.length != 1 || handle_state.held[0].name != id) {
    103          message = "No first-party Web Lock access";
    104        }
    105        const local_state = await window.navigator.locks.query();
    106        if (local_state.held.length != 0) {
    107          message = "Handle should not override window Web Locks";
    108        }
    109        await handle.locks.request(id, {steal: true}, async () => {});
    110        break;
    111      }
    112      case "caches": {
    113        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({caches: true}));
    114        const handle_has = await handle.caches.has(id);
    115        if (!handle_has) {
    116          message = "No first-party Cache Storage access";
    117        }
    118        const local_has = await window.caches.has(id);
    119        if (local_has) {
    120          message = "Handle should not override window Cache Storage";
    121        }
    122        document.cookie = "partitioned=test; SameSite=None; Secure; Partitioned;";
    123        const cache = await handle.caches.open(id);
    124        await cache.add("/storage-access-api/resources/get_cookies.py?2");
    125        await test_driver.bless("fake user interaction", () => document.requestStorageAccess());
    126        await cache.add("/storage-access-api/resources/get_cookies.py?3");
    127        let req = await cache.match("/storage-access-api/resources/get_cookies.py?1");
    128        let req_json = await req.json();
    129        if (!req_json.hasOwnProperty("samesite_strict")) {
    130          message = "Top-level cache fetch should have SameSite=Strict cookies.";
    131        }
    132        if (!req_json.hasOwnProperty("samesite_lax")) {
    133          message = "Top-level cache fetch should have SameSite=Lax cookies.";
    134        }
    135        if (!req_json.hasOwnProperty("samesite_none")) {
    136          message = "Top-level cache fetch should have SameSite=None cookies.";
    137        }
    138        if (req_json.hasOwnProperty("partitioned")) {
    139          message = "Top-level cache fetch should not have partitioned cookies.";
    140        }
    141        req = await cache.match("/storage-access-api/resources/get_cookies.py?2");
    142        req_json = await req.json();
    143        if (req_json.hasOwnProperty("samesite_strict")) {
    144          message = "SAA cache fetch should not have SameSite=Strict cookies.";
    145        }
    146        if (req_json.hasOwnProperty("samesite_lax")) {
    147          message = "SAA cache fetch should not have SameSite=Lax cookies.";
    148        }
    149        // Note: no assertion about default third-party cookie behavior.
    150        if (!req_json.hasOwnProperty("partitioned")) {
    151          message = "SAA cache fetch should have partitioned cookies.";
    152        }
    153        req = await cache.match("/storage-access-api/resources/get_cookies.py?3");
    154        req_json = await req.json();
    155        if (req_json.hasOwnProperty("samesite_strict")) {
    156          message = "SAA cache + cookie fetch should not have SameSite=Strict cookies.";
    157        }
    158        if (req_json.hasOwnProperty("samesite_lax")) {
    159          message = "SAA cache + cookie fetch should not have SameSite=Lax cookies.";
    160        }
    161        if (!req_json.hasOwnProperty("samesite_none")) {
    162          message = "SAA cache + cookie fetch should have SameSite=None cookies.";
    163        }
    164        if (!req_json.hasOwnProperty("partitioned")) {
    165          message = "SAA cache + cookie fetch should have partitioned cookies.";
    166        }
    167        await handle.caches.delete(id);
    168        break;
    169      }
    170      case "getDirectory": {
    171        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({getDirectory: true}));
    172        const handle_root = await handle.getDirectory();
    173        let handle_has = await handle_root.getFileHandle(id).then(() => true, () => false);
    174        if (!handle_has) {
    175          message = "No first-party Origin Private File System access";
    176        }
    177        const local_root = await window.navigator.storage.getDirectory();
    178        let local_has = await local_root.getFileHandle(id).then(() => true, () => false);
    179        if (local_has) {
    180          message = "Handle should not override window Origin Private File System";
    181        }
    182        await handle_root.removeEntry(id);
    183        break;
    184      }
    185      case "estimate": {
    186        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({estimate: true}));
    187        const handle_estimate = await handle.estimate();
    188        if (handle_estimate.usage  <= 0) {
    189          message = "No first-party quota access";
    190        }
    191        const local_estimate = await window.navigator.storage.estimate();
    192        if (local_estimate > 0) {
    193          message = "Handle should not override window quota";
    194        }
    195        break;
    196      }
    197      case "blobStorage": {
    198        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({createObjectURL: true, revokeObjectURL: true}));
    199        let blob = await fetch(atob(id)).then(
    200          (response) => response.text(),
    201          () => "");
    202        if (blob != "TEST") {
    203          message = "Blob storage should be readable in this context";
    204        }
    205        URL.revokeObjectURL(atob(id));
    206        blob = await fetch(atob(id)).then(
    207          (response) => response.text(),
    208          () => "");
    209        if (blob != "TEST") {
    210          message = "Handle should not override window blob storage";
    211        }
    212        handle.revokeObjectURL(atob(id));
    213        blob = await fetch(atob(id)).then(
    214          (response) => response.text(),
    215          () => "");
    216        if (blob != "") {
    217          message = "No first-party blob access";
    218        }
    219        const url = handle.createObjectURL(new Blob(["TEST2"]));
    220        blob = await fetch(url).then(
    221          (response) => response.text(),
    222          () => "");
    223        if (blob != "TEST2") {
    224          message = "A blob url created via the handle should be readable";
    225        }
    226        handle.revokeObjectURL(url);
    227        blob = await fetch(url).then(
    228          (response) => response.text(),
    229          () => "");
    230        if (blob != "") {
    231          message = "A blob url removed via the handle should be cleared";
    232        }
    233        break;
    234      }
    235      case "BroadcastChannel": {
    236        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({BroadcastChannel: true}));
    237        const handle_channel = handle.BroadcastChannel(id);
    238        handle_channel.postMessage("Same-origin handle access");
    239        handle_channel.close();
    240        const local_channel = new BroadcastChannel(id);
    241        local_channel.postMessage("Same-origin local access");
    242        local_channel.close();
    243        break;
    244      }
    245      case "SharedWorker": {
    246        const local_shared_worker = new SharedWorker("/storage-access-api/resources/shared-worker-relay.js", id);
    247        local_shared_worker.port.start();
    248        local_shared_worker.port.postMessage("Same-origin local access");
    249        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({SharedWorker: true}));
    250        let couldRequestAllCookies = true;
    251        try {
    252          handle.SharedWorker("/storage-access-api/resources/shared-worker-relay.js", {name: id, sameSiteCookies: 'all'});
    253        } catch (_) {
    254          couldRequestAllCookies = false;
    255        }
    256        if (couldRequestAllCookies) {
    257          message = "Shared Workers in a third-party context should not be able to request SameSite cookies.";
    258        }
    259        handle.SharedWorker("/storage-access-api/resources/shared-worker-cookies.py", id).port.start();
    260        const handle_shared_worker = handle.SharedWorker("/storage-access-api/resources/shared-worker-relay.js", {name: id, sameSiteCookies: 'none'});
    261        handle_shared_worker.port.start();
    262        handle_shared_worker.port.postMessage("Same-origin handle access");
    263        break;
    264      }
    265      case "BlobURLDedicatedWorker": {
    266        const fetch_unsuccessful_response = "fetch_unsuccessful";
    267        const fetch_successful_response = "fetch_successful";
    268 
    269        const can_blob_url_be_fetched_js = `
    270          onmessage = async (e) => {
    271            const blob_url = e.data;
    272            try {
    273              const blob = await fetch(blob_url).then(response => response.blob());
    274              await blob.text();
    275              postMessage("${fetch_successful_response}");
    276            } catch(e) {
    277              postMessage("${fetch_unsuccessful_response}");
    278            }
    279          };
    280        `;
    281 
    282        // case 1: create dedicated worker w/o granting storage access
    283        const worker_blob_url = new Blob([can_blob_url_be_fetched_js], { type: 'text/javascript' });
    284        const third_party_blob_url = URL.createObjectURL(worker_blob_url);
    285 
    286        const worker_1 = new Worker(third_party_blob_url);
    287 
    288        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({all: true}));
    289 
    290        const worker_blob = new Blob(["potato"]);
    291        const first_party_blob_url = handle.createObjectURL(worker_blob);
    292 
    293        const worker_response_promise = new Promise((resolve) => {
    294          worker_1.onmessage = (e) => { resolve(e.data) };
    295          worker_1.postMessage(first_party_blob_url);
    296        });
    297 
    298        const worker_response = await worker_response_promise;
    299        if (worker_response === fetch_unsuccessful_response) {
    300          message = "Dedicated worker expectedly failed fetching first-party blob URL from a third-party context without granting storage access.";
    301        } else if (worker_response === fetch_successful_response) {
    302          message = "Dedicated worker unexpectedly fetched first-party blob URL from a third-party context without granting storage access.";
    303          break;
    304        }
    305 
    306        // case 2: create dedicated worker after storage access is granted
    307        const worker_2 = new Worker(third_party_blob_url);
    308        const worker_response_promise2 = new Promise((resolve) => {
    309          worker_2.onmessage = (e) => { resolve(e.data) };
    310          worker_2.postMessage(first_party_blob_url);
    311        });
    312        const worker_response2 = await worker_response_promise2;
    313 
    314        URL.revokeObjectURL(third_party_blob_url);
    315        handle.revokeObjectURL(first_party_blob_url);
    316        worker_2.terminate();
    317 
    318        if (worker_response2 === fetch_unsuccessful_response) {
    319          message = "Dedicated worker unexpectedly failed fetching first-party blob URL from a third-party context with granting storage access.";
    320          break;
    321        } else if (worker_response2 === fetch_successful_response) {
    322          message = "Blob URL DedicatedWorker tests completed successfully.";
    323        }
    324        break;
    325      }
    326      case "ThirdPartyBlobURL": {
    327        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({all: true}));
    328        try {
    329          const blob = await fetch(atob(id)).then(response => response.blob());
    330          await blob.text();
    331        } catch (e) {
    332          message = "Third Party Blob URL tests completed successfully.";
    333          break;
    334        }
    335        message = "Cross-partition third-party blob URL was accessible after granting storage access";
    336        break;
    337      }
    338      case "BlobURLSharedWorker": {
    339        const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({createObjectURL: true, revokeObjectURL: true, SharedWorker: true}));
    340        const workerScript = await fetch('/storage-access-api/resources/shared-worker-relay.js').then(response => response.text());
    341        const workerBlob = new Blob([workerScript], { type: 'text/javascript' });
    342 
    343        const firstPartyBlobUrl = handle.createObjectURL(workerBlob);
    344        const thirdPartyWorker = new SharedWorker(firstPartyBlobUrl);
    345        var workerCreated = true;
    346        await new Promise((resolve) => {
    347          thirdPartyWorker.onerror = (event) => {
    348            message = "Third-party SharedWorker not created from first-party blob URL unexpectedly.";
    349            workerCreated = false;
    350            resolve();
    351          }
    352          thirdPartyWorker.port.onmessage = (event) => {
    353            message = "Third-party SharedWorker created from first-party blob URL.";
    354            resolve();
    355          }
    356          thirdPartyWorker.port.postMessage("ping");
    357        });
    358        if (!workerCreated) {
    359          break;
    360        }
    361 
    362        const firstPartyWorker = handle.SharedWorker(firstPartyBlobUrl);
    363        await new Promise((resolve) => {
    364          firstPartyWorker.onerror = (event) => {
    365            message = "First-party SharedWorker not created from first-party blob URL unexpectedly.";
    366            workerCreated = false;
    367            resolve();
    368          }
    369          firstPartyWorker.port.onmessage = (event) => {
    370            message = "First-party SharedWorker created from first-party blob URL.";
    371            resolve();
    372          }
    373          firstPartyWorker.port.postMessage("ping");
    374        });
    375        if (!workerCreated) {
    376          break;
    377        }
    378 
    379        const thirdPartyBlobUrl = URL.createObjectURL(workerBlob);
    380        const worker = handle.SharedWorker(thirdPartyBlobUrl);
    381        await new Promise((resolve) => {
    382          worker.onerror = (event) => {
    383            message = "Blob URL SharedWorker tests completed successfully.";
    384            workerCreated = false;
    385            resolve();
    386          };
    387          worker.port.onmessage = (event) => {
    388            message = "First-party SharedWorker created from third-party blob URL unexpectedly.";
    389            resolve();
    390          }
    391          worker.port.postMessage("ping");
    392        });
    393        break;
    394      }
    395      default: {
    396        message = "Unexpected type " + type;
    397        break;
    398      }
    399    }
    400  } catch (_) {
    401    message = "Unable to load handle in same-origin context for " + type;
    402  }
    403  // Step 7 (storage-access-api/storage-access-beyond-cookies.{}.sub.https.html)
    404  await test_driver.set_permission({ name: 'storage-access' }, 'prompt');
    405  window.top.postMessage({type: "result", message: message}, "*");
    406 })();
    407 </script>