Bug 1382260 - Patch 2 - [Mac] Allow reading of font files from the content sandbox. r=Alex_Gaynor draft
authorHaik Aftandilian <haftandilian@mozilla.com>
Fri, 18 Aug 2017 16:12:07 -0700
changeset 651435 9aa778bc08bee206e7f3340eac32ca2f46a4f81b
parent 650730 5fba75d65081e56df5a0d171c41689c489a3aace
child 727715 e989283c8eff3000e199ba42448b6845d33b2081
push id75729
push userhaftandilian@mozilla.com
push dateWed, 23 Aug 2017 18:24:02 +0000
reviewersAlex_Gaynor
bugs1382260
milestone57.0a1
Bug 1382260 - Patch 2 - [Mac] Allow reading of font files from the content sandbox. r=Alex_Gaynor MozReview-Commit-ID: 9W5aqQweFmd
security/sandbox/mac/SandboxPolicies.h
security/sandbox/test/browser_content_sandbox_fs.js
security/sandbox/test/browser_content_sandbox_utils.js
--- a/security/sandbox/mac/SandboxPolicies.h
+++ b/security/sandbox/mac/SandboxPolicies.h
@@ -337,13 +337,26 @@ static const char contentSandboxRules[] 
   (allow file-read* file-write-data
     (subpath appTempDir))
   (allow file-write-create
     (require-all
       (subpath appTempDir)
       (require-any
         (vnode-type REGULAR-FILE)
         (vnode-type DIRECTORY))))
+
+  ; bug 1382260
+  ; We may need to load fonts from outside of the standard
+  ; font directories whitelisted above. This is typically caused
+  ; by a font manager. For now, whitelist any file with a
+  ; font extension. Limit this to the common font types:
+  ; files ending in .otf, .ttf, .ttc, .otc, and .dfont.
+  (allow file-read*
+    (regex #"\.[oO][tT][fF]$"           ; otf
+           #"\.[tT][tT][fF]$"           ; ttf
+           #"\.[tT][tT][cC]$"           ; ttc
+           #"\.[oO][tT][cC]$"           ; otc
+           #"\.[dD][fF][oO][nN][tT]$")) ; dfont
 )";
 
 }
 
 #endif // mozilla_SandboxPolicies_h
--- a/security/sandbox/test/browser_content_sandbox_fs.js
+++ b/security/sandbox/test/browser_content_sandbox_fs.js
@@ -4,37 +4,38 @@
  "use strict";
 
 var prefs = Cc["@mozilla.org/preferences-service;1"]
             .getService(Ci.nsIPrefBranch);
 
 Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/" +
     "security/sandbox/test/browser_content_sandbox_utils.js", this);
 
+const FONT_EXTENSIONS = [ "otf", "ttf", "ttc", "otc", "dfont" ];
+
 /*
  * This test exercises file I/O from web and file content processes using
  * OS.File methods to validate that calls that are meant to be blocked by
  * content sandboxing are blocked.
  */
 
 // Creates file at |path| and returns a promise that resolves with true
 // if the file was successfully created, otherwise false. Include imports
 // so this can be safely serialized and run remotely by ContentTask.spawn.
 function createFile(path) {
   Components.utils.import("resource://gre/modules/osfile.jsm");
   let encoder = new TextEncoder();
-  let array = encoder.encode("WRITING FROM CONTENT PROCESS");
+  let array = encoder.encode("TEST FILE DUMMY DATA");
   return OS.File.writeAtomic(path, array).then(function(value) {
     return true;
   }, function(reason) {
     return false;
   });
 }
 
-
 // Creates a symlink at |path| and returns a promise that resolves with true
 // if the symlink was successfully created, otherwise false. Include imports
 // so this can be safely serialized and run remotely by ContentTask.spawn.
 function createSymlink(path) {
   Components.utils.import("resource://gre/modules/osfile.jsm");
   // source location for the symlink can be anything
   return OS.File.unixSymLink("/Users", path).then(function(value) {
     return true;
@@ -241,16 +242,56 @@ async function createTempFile() {
     let symlinkCreated = await ContentTask.spawn(browser, path, createSymlink);
     ok(symlinkCreated == false,
        "created a symlink in content temp is not permitted");
   } else {
     ok(fileDeleted == true, "deleting a file in content temp is permitted");
   }
 }
 
+// Build a list of nonexistent font file paths (lower and upper case) with
+// all the font extensions we want the sandbox to allow read access to.
+// Generate paths within base directory |baseDir|.
+function getFontTestPaths(baseDir) {
+  baseDir = baseDir + "/";
+
+  let basename = uuid();
+  let testPaths = [];
+
+  for (let ext of FONT_EXTENSIONS) {
+    // lower case filename
+    let lcFilename = baseDir + (basename + "lc." + ext).toLowerCase();
+    testPaths.push(lcFilename);
+    // upper case filename
+    let ucFilename = baseDir + (basename + "UC." + ext).toUpperCase();
+    testPaths.push(ucFilename);
+  }
+  return testPaths;
+}
+
+// Build a list of nonexistent invalid font file paths. Specifically,
+// paths that include the valid font extensions but should fail to load.
+// For example, if a font extension happens to be a substring of the filename
+// but isn't the extension. Generate paths within base directory |baseDir|.
+function getBadFontTestPaths(baseDir) {
+  baseDir = baseDir + "/";
+
+  let basename = uuid();
+  let testPaths = [];
+
+  for (let ext of FONT_EXTENSIONS) {
+    let filename = baseDir + basename + "." + ext + ".txt";
+    testPaths.push(filename);
+
+    filename = baseDir + basename + "." + ext + ext + ".txt";
+    testPaths.push(filename);
+  }
+  return testPaths;
+}
+
 // Test reading files and dirs from web and file content processes.
 async function testFileAccess() {
   // for tests that run in a web content process
   let webBrowser = gBrowser.selectedBrowser;
 
   // Ensure that the file content process is enabled.
   let fileContentProcessEnabled =
     prefs.getBoolPref("browser.tabs.remote.separateFileUriProcess");
@@ -271,16 +312,63 @@ async function testFileAccess() {
 
   // Directories/files to test accessing from content processes.
   // For directories, we test whether a directory listing is allowed
   // or blocked. For files, we test if we can read from the file.
   // Each entry in the array represents a test file or directory
   // that will be read from either a web or file process.
   let tests = [];
 
+  // Test that Mac content processes can read files with font extensions
+  // and fail to read files that include the font extension as a
+  // non-extension substring.
+  if (isMac()) {
+    // Use the same directory for valid/invalid font path tests to ensure
+    // the font isn't allowed because the directory is already allowed.
+    let fontTestDir = "/private/tmp";
+    let fontTestPaths = getFontTestPaths(fontTestDir);
+    let badFontTestPaths = getBadFontTestPaths(fontTestDir);
+
+    // before we start creating dummy font files,
+    // register a cleanup func to remove them
+    registerCleanupFunction(async function() {
+      for (let fontPath of fontTestPaths.concat(badFontTestPaths)) {
+        await OS.File.remove(fontPath, {ignoreAbsent: true});
+      }
+    });
+
+    // create each dummy font file and add a test for it
+    for (let fontPath of fontTestPaths) {
+      let result = await createFile(fontPath);
+      Assert.ok(result, `${fontPath} created`);
+
+      let fontFile = GetFile(fontPath);
+      tests.push({
+        desc:     "font file",                  // description
+        ok:       true,                         // expected to succeed?
+        browser:  webBrowser,                   // browser to run test in
+        file:     fontFile,                     // nsIFile object
+        minLevel: minHomeReadSandboxLevel(),    // min level to enable test
+      });
+    }
+    for (let fontPath of badFontTestPaths) {
+      let result = await createFile(fontPath);
+      Assert.ok(result, `${fontPath} created`);
+
+      let fontFile = GetFile(fontPath);
+      tests.push({
+        desc:     "invalid font file",          // description
+        ok:       false,                        // expected to succeed?
+        browser:  webBrowser,                   // browser to run test in
+        file:     fontFile,                     // nsIFile object
+        minLevel: minHomeReadSandboxLevel(),    // min level to enable test
+      });
+    }
+  }
+
   // The Linux test runners create the temporary profile in the same
   // system temp dir we give write access to, so this gives a false
   // positive.
   let profileDir = GetProfileDir();
   if (!isLinux()) {
     tests.push({
       desc:     "profile dir",                // description
       ok:       false,                        // expected to succeed?
--- a/security/sandbox/test/browser_content_sandbox_utils.js
+++ b/security/sandbox/test/browser_content_sandbox_utils.js
@@ -84,8 +84,14 @@ function GetDir(path) {
   dir.initWithPath(path);
   Assert.ok(dir.isDirectory(), `${path} is a directory`);
   return (dir);
 }
 
 function GetDirFromEnvVariable(varName) {
   return GetDir(environment.get(varName));
 }
+
+function GetFile(path) {
+  let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+  file.initWithPath(path);
+  return (file);
+}