Commit 8f780000 authored by Trent Apted's avatar Trent Apted Committed by Commit Bot

Disallow dereferencing nulls in chrome://media-app JS

This involves adding a conformance config to enforce the rule provided
by com.google.javascript.jscomp.ConformanceRules$BanNullDeref.

Fix the current non-conformance in release code. A follow-up will fix
the test code.

One instance in the Message.SAVE_COPY handler was likely a real bug.

Bug: 1068428, b/156695190
Change-Id: I80cb6b78b8b734cc272e636a1c680066af8ef0b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2203348
Commit-Queue: Trent Apted <tapted@chromium.org>
Reviewed-by: default avatardstockwell <dstockwell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769625}
parent ead24941
...@@ -7,6 +7,11 @@ import("//third_party/closure_compiler/compile_js.gni") ...@@ -7,6 +7,11 @@ import("//third_party/closure_compiler/compile_js.gni")
# Note we compile with reportUnknownTypes while it works, but if dependencies # Note we compile with reportUnknownTypes while it works, but if dependencies
# get more complex, we should remove it and only enable in developer builds. # get more complex, we should remove it and only enable in developer builds.
media_closure_flags = default_closure_args + [ media_closure_flags = default_closure_args + [
"conformance_configs " + rebase_path(
"../../../web_applications/closure_conformance_checks.txt",
root_build_dir),
"jscomp_error=conformanceViolations",
"jscomp_error=strictCheckTypes", "jscomp_error=strictCheckTypes",
"jscomp_error=reportUnknownTypes", "jscomp_error=reportUnknownTypes",
"language_in=ECMASCRIPT_2018", "language_in=ECMASCRIPT_2018",
......
...@@ -73,31 +73,33 @@ guestMessagePipe.registerHandler(Message.OVERWRITE_FILE, async (message) => { ...@@ -73,31 +73,33 @@ guestMessagePipe.registerHandler(Message.OVERWRITE_FILE, async (message) => {
guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => { guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => {
const deleteMsg = /** @type{DeleteFileMessage} */ (message); const deleteMsg = /** @type{DeleteFileMessage} */ (message);
assertFileAndDirectoryMutable(deleteMsg.token, 'Delete'); const {file, directory} =
assertFileAndDirectoryMutable(deleteMsg.token, 'Delete');
if (!(await isCurrentHandleInCurrentDirectory())) { if (!(await isCurrentHandleInCurrentDirectory())) {
return {deleteResult: DeleteResult.FILE_MOVED}; return {deleteResult: DeleteResult.FILE_MOVED};
} }
// Get the name from the file reference. Handles file renames. // Get the name from the file reference. Handles file renames.
const currentFilename = (await currentlyWritableFile.handle.getFile()).name; const currentFilename = (await file.handle.getFile()).name;
await currentDirectoryHandle.removeEntry(currentFilename); await directory.removeEntry(currentFilename);
return {deleteResult: DeleteResult.SUCCESS}; return {deleteResult: DeleteResult.SUCCESS};
}); });
/** Handler to rename the currently focused file. */ /** Handler to rename the currently focused file. */
guestMessagePipe.registerHandler(Message.RENAME_FILE, async (message) => { guestMessagePipe.registerHandler(Message.RENAME_FILE, async (message) => {
const renameMsg = /** @type{RenameFileMessage} */ (message); const renameMsg = /** @type{RenameFileMessage} */ (message);
assertFileAndDirectoryMutable(renameMsg.token, 'Rename'); const {file, directory} =
assertFileAndDirectoryMutable(renameMsg.token, 'Rename');
if (await filenameExistsInCurrentDirectory(renameMsg.newFilename)) { if (await filenameExistsInCurrentDirectory(renameMsg.newFilename)) {
return {renameResult: RenameResult.FILE_EXISTS}; return {renameResult: RenameResult.FILE_EXISTS};
} }
const originalFile = await currentlyWritableFile.handle.getFile(); const originalFile = await file.handle.getFile();
const renamedFileHandle = await currentDirectoryHandle.getFile( const renamedFileHandle =
renameMsg.newFilename, {create: true}); await directory.getFile(renameMsg.newFilename, {create: true});
// Copy file data over to the new file. // Copy file data over to the new file.
const writer = await renamedFileHandle.createWritable(); const writer = await renamedFileHandle.createWritable();
// TODO(b/153021155): Use originalFile.stream(). // TODO(b/153021155): Use originalFile.stream().
...@@ -110,12 +112,12 @@ guestMessagePipe.registerHandler(Message.RENAME_FILE, async (message) => { ...@@ -110,12 +112,12 @@ guestMessagePipe.registerHandler(Message.RENAME_FILE, async (message) => {
// success, we first check the `currentlyWritableFile.handle` is the same as // success, we first check the `currentlyWritableFile.handle` is the same as
// the handle for the file with that filename in the `currentDirectoryHandle`. // the handle for the file with that filename in the `currentDirectoryHandle`.
if (await isCurrentHandleInCurrentDirectory()) { if (await isCurrentHandleInCurrentDirectory()) {
await currentDirectoryHandle.removeEntry(originalFile.name); await directory.removeEntry(originalFile.name);
} }
// Reload current file so it is in an editable state, this is done before // Reload current file so it is in an editable state, this is done before
// removing the old file so the relaunch starts sooner. // removing the old file so the relaunch starts sooner.
await launchWithDirectory(currentDirectoryHandle, renamedFileHandle); await launchWithDirectory(directory, renamedFileHandle);
return {renameResult: RenameResult.SUCCESS}; return {renameResult: RenameResult.SUCCESS};
}); });
...@@ -155,7 +157,9 @@ guestMessagePipe.registerHandler(Message.SAVE_COPY, async (message) => { ...@@ -155,7 +157,9 @@ guestMessagePipe.registerHandler(Message.SAVE_COPY, async (message) => {
} }
const {handle} = await getFileFromHandle(fileSystemHandle); const {handle} = await getFileFromHandle(fileSystemHandle);
if (await handle.isSameEntry(currentlyWritableFile.handle)) { // Note there may be no currently writable file (e.g. save from clipboard).
if (currentlyWritableFile &&
await handle.isSameEntry(currentlyWritableFile.handle)) {
return 'attemptedCurrentlyWritableFileOverwrite'; return 'attemptedCurrentlyWritableFileOverwrite';
} }
...@@ -274,6 +278,7 @@ async function sendSnapshotToGuest(snapshot) { ...@@ -274,6 +278,7 @@ async function sendSnapshotToGuest(snapshot) {
* the file to be mutated is incorrect. * the file to be mutated is incorrect.
* @param {number} editFileToken * @param {number} editFileToken
* @param {string} operation * @param {string} operation
* @return {{file: !FileDescriptor, directory: !FileSystemDirectoryHandle}}
*/ */
function assertFileAndDirectoryMutable(editFileToken, operation) { function assertFileAndDirectoryMutable(editFileToken, operation) {
if (!currentlyWritableFile || editFileToken !== fileToken) { if (!currentlyWritableFile || editFileToken !== fileToken) {
...@@ -283,6 +288,7 @@ function assertFileAndDirectoryMutable(editFileToken, operation) { ...@@ -283,6 +288,7 @@ function assertFileAndDirectoryMutable(editFileToken, operation) {
if (!currentDirectoryHandle) { if (!currentDirectoryHandle) {
throw new Error(`${operation} failed. File without launch directory.`); throw new Error(`${operation} failed. File without launch directory.`);
} }
return {file: currentlyWritableFile, directory: currentDirectoryHandle};
} }
/** /**
...@@ -291,6 +297,9 @@ function assertFileAndDirectoryMutable(editFileToken, operation) { ...@@ -291,6 +297,9 @@ function assertFileAndDirectoryMutable(editFileToken, operation) {
* @return {!Promise<!boolean>} * @return {!Promise<!boolean>}
*/ */
async function isCurrentHandleInCurrentDirectory() { async function isCurrentHandleInCurrentDirectory() {
if (!currentlyWritableFile) {
return false;
}
// Get the name from the file reference. Handles file renames. // Get the name from the file reference. Handles file renames.
const currentFilename = (await currentlyWritableFile.handle.getFile()).name; const currentFilename = (await currentlyWritableFile.handle.getFile()).name;
const fileHandle = await getFileHandleFromCurrentDirectory(currentFilename); const fileHandle = await getFileHandleFromCurrentDirectory(currentFilename);
...@@ -316,6 +325,9 @@ async function filenameExistsInCurrentDirectory(filename) { ...@@ -316,6 +325,9 @@ async function filenameExistsInCurrentDirectory(filename) {
*/ */
async function getFileHandleFromCurrentDirectory( async function getFileHandleFromCurrentDirectory(
filename, suppressError = false) { filename, suppressError = false) {
if (!currentDirectoryHandle) {
return null;
}
try { try {
return (await currentDirectoryHandle.getFile(filename, {create: false})); return (await currentDirectoryHandle.getFile(filename, {create: false}));
} catch (/** @type {Object} */ e) { } catch (/** @type {Object} */ e) {
...@@ -376,7 +388,7 @@ function isFileRelated(focusFile, siblingFile) { ...@@ -376,7 +388,7 @@ function isFileRelated(focusFile, siblingFile) {
* @param {?File} focusFile * @param {?File} focusFile
*/ */
async function setCurrentDirectory(directory, focusFile) { async function setCurrentDirectory(directory, focusFile) {
if (!focusFile) { if (!focusFile || !focusFile.name) {
return; return;
} }
currentFiles.length = 0; currentFiles.length = 0;
...@@ -399,7 +411,8 @@ async function setCurrentDirectory(directory, focusFile) { ...@@ -399,7 +411,8 @@ async function setCurrentDirectory(directory, focusFile) {
currentFiles.push({token: -1, file: entry.file, handle: entry.handle}); currentFiles.push({token: -1, file: entry.file, handle: entry.handle});
} }
} }
entryIndex = currentFiles.findIndex(i => i.file.name === focusFile.name); const name = focusFile.name;
entryIndex = currentFiles.findIndex(i => !!i.file && i.file.name === name);
currentDirectoryHandle = directory; currentDirectoryHandle = directory;
} }
...@@ -407,7 +420,7 @@ async function setCurrentDirectory(directory, focusFile) { ...@@ -407,7 +420,7 @@ async function setCurrentDirectory(directory, focusFile) {
* Launch the media app with the files in the provided directory, using `handle` * Launch the media app with the files in the provided directory, using `handle`
* as the initial launch entry. * as the initial launch entry.
* @param {!FileSystemDirectoryHandle} directory * @param {!FileSystemDirectoryHandle} directory
* @param {?FileSystemHandle} handle * @param {!FileSystemHandle} handle
*/ */
async function launchWithDirectory(directory, handle) { async function launchWithDirectory(directory, handle) {
let asFile; let asFile;
...@@ -444,18 +457,23 @@ async function advance(direction) { ...@@ -444,18 +457,23 @@ async function advance(direction) {
// Wait for 'load' (and not DOMContentLoaded) to ensure the subframe has been // Wait for 'load' (and not DOMContentLoaded) to ensure the subframe has been
// loaded and is ready to respond to postMessage. // loaded and is ready to respond to postMessage.
window.addEventListener('load', () => { window.addEventListener('load', () => {
if (!window.launchQueue) {
console.error('FileHandling API missing.');
return;
}
window.launchQueue.setConsumer(params => { window.launchQueue.setConsumer(params => {
if (!params || !params.files || params.files.length < 2) { if (!params || !params.files || params.files.length < 2) {
console.error('Invalid launch (missing files): ', params); console.error('Invalid launch (missing files): ', params);
return; return;
} }
if (!params.files[0].isDirectory) { if (!assertCast(params.files[0]).isDirectory) {
console.error('Invalid launch: files[0] is not a directory: ', params); console.error('Invalid launch: files[0] is not a directory: ', params);
return; return;
} }
const directory = const directory =
/** @type{!FileSystemDirectoryHandle} */ (params.files[0]); /** @type{!FileSystemDirectoryHandle} */ (params.files[0]);
launchWithDirectory(directory, params.files[1]); const focusEntry = assertCast(params.files[1]);
launchWithDirectory(directory, focusEntry);
}); });
}); });
# JS Conformance rules for ChromeOS Essential Apps.
# See https://github.com/google/closure-compiler/wiki/JS-Conformance-Framework
requirement: {
java_class: "com.google.javascript.jscomp.ConformanceRules$BanNullDeref"
error_message: "Attempt to dereference null or undefined."
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment