Firefox · Toolkit
CVE-2026-74983
Logic Error in Toolkit
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
add_tasktoolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js |
modified | |
iftoolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js |
modified |
Files Changed
toolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.jstoolkit/components/contentanalysis/tests/browser/browser_download_save_link_as_content_analysis.jstoolkit/components/contentanalysis/tests/browser/head.jstoolkit/components/downloads/DownloadIntegration.sys.mjs
Patch
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js
index 0b578d8a5b5..bad2fa0a975 100644
--- a/toolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js
+++ b/toolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js
@@ -12,9 +12,9 @@ add_setup(async function test_setup() {
const DOWNLOAD_URL =
"https://example.com/browser/toolkit/components/contentanalysis/tests/browser/file_to_download.unknownextension";
-async function createTargetFileAndDownload() {
+async function createTargetFileAndDownload(usePartFile) {
// Create a temporary file that will be downloaded to.
- const targetFile = await IOUtils.createUniqueFile(
+ const targetFileName = await IOUtils.createUniqueFile(
PathUtils.tempDir,
"target_download_for_content_analysis.txt",
0o600
@@ -23,13 +23,16 @@ async function createTargetFileAndDownload() {
// successfully downloaded. Note that this theoretically introduces
// a race condition since something else could come in and create
// a temp file with the same name. This seems unlikely.
- await IOUtils.remove(targetFile, { ignoreAbsent: false });
+ await IOUtils.remove(targetFileName, { ignoreAbsent: false });
return await Downloads.createDownload({
source: {
url: DOWNLOAD_URL,
},
- target: targetFile,
+ target: {
+ path: targetFileName,
+ partFilePath: usePartFile ? targetFileName + ".part" : undefined,
+ },
});
}
@@ -90,7 +93,7 @@ function assertContentAnalysisDownloadRequest(request, expectedFilePath) {
ok(!!request.requestToken.length, "request requestToken should not be empty");
}
-add_task(async function test_download_content_analysis_allows() {
+async function check_download_content_analysis_allows(usePartFile) {
mockCA.setupForTest(true);
await SpecialPowers.pushPrefEnv({
set: [
@@ -98,7 +101,7 @@ add_task(async function test_download_content_analysis_allows() {
],
});
- const download = await createTargetFileAndDownload();
+ const download = await createTargetFileAndDownload(usePartFile);
let list = await Downloads.getList(Downloads.PUBLIC);
await list.add(download);
await download.start();
@@ -106,13 +109,33 @@ add_task(async function test_download_content_analysis_allows() {
// Make sure the download succeeded.
ok(download.succeeded, "Download should succeed");
is(mockCA.calls.length, 1, "Content analysis should be called once");
- assertContentAnalysisDownloadRequest(mockCA.calls[0], download.target.path);
- ok(await IOUtils.exists(download.target.path), "Target file should exist");
+ assertContentAnalysisDownloadRequest(
+ mockCA.calls[0],
+ usePartFile ? download.target.partFilePath : download.target.path
+ );
+ ok(
+ await IOUtils.exists(download.target.path),
+ "Final target file should exist"
+ );
+ if (usePartFile) {
+ ok(
+ !(await IOUtils.exists(download.target.partFilePath)),
+ "Part file should not still exist"
+ );
+ }
await IOUtils.remove(download.target.path);
await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function test_download_content_analysis_allows() {
+ await check_download_content_analysis_allows(false /* usePartFile */);
});
-add_task(async function test_download_content_analysis_blocks() {
+add_task(async function test_download_content_analysis_allows_partfile() {
+ await check_download_content_analysis_allows(true /* usePartFile */);
+});
+
+async function check_download_content_analysis_blocks(usePartFile) {
mockCA.setupForTest(false);
await SpecialPowers.pushPrefEnv({
set: [
@@ -120,28 +143,45 @@ add_task(async function test_download_content_analysis_blocks() {
],
});
- const download = await createTargetFileAndDownload();
- info(`path is ${download.target.path}`);
+ const download = await createTargetFileAndDownload(usePartFile);
+ if (usePartFile) {
+ info(`part file path is ${download.target.partFilePath}`);
+ }
+ info(`final path is ${download.target.path}`);
await Assert.rejects(
download.start(),
ex => ex instanceof Downloads.Error && ex.becauseBlockedByContentAnalysis,
"Download should have been rejected"
);
- info(`path is ${download.target.path}`);
ok(!download.succeeded, "Download should not succeed");
is(mockCA.calls.length, 1, "Content analysis should be called once");
- info(`path is ${download.target.path}`);
- assertContentAnalysisDownloadRequest(mockCA.calls[0], download.target.path);
- info(`path is ${download.target.path}`);
+ assertContentAnalysisDownloadRequest(
+ mockCA.calls[0],
+ usePartFile ? download.target.partFilePath : download.target.path
+ );
ok(
!(await IOUtils.exists(download.target.path)),
- "Target file should not exist"
+ "Final target file should not exist"
);
+ if (usePartFile) {
+ ok(
+ !(await IOUtils.exists(download.target.partFilePath)),
+ "Part file should not still exist"
+ );
+ }
await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function test_download_content_analysis_blocks() {
+ await check_download_content_analysis_blocks(false /* usePartFile */);
});
-add_task(async function test_download_content_analysis_user_cancels() {
+add_task(async function test_download_content_analysis_blocks_partfile() {
+ await check_download_content_analysis_blocks(true /* usePartFile */);
+});
+
+async function check_download_content_analysis_user_cancels(usePartFile) {
// Make the mock CA service wait for event so the test can
// cancel the download before the scan finishes.
mockCA.setupForTest(true, /* waitForEvent */ true);
@@ -156,9 +196,13 @@ add_task(async function test_download_content_analysis_user_cancels() {
once: true,
});
});
- const download = await createTargetFileAndDownload();
+ const download = await createTargetFileAndDownload(usePartFile);
+
+ if (usePartFile) {
+ info(`part file path is ${download.target.partFilePath}`);
+ }
+ info(`final path is ${download.target.path}`);
- info(`path is ${download.target.path}`);
let list = await Downloads.getList(Downloads.PUBLIC);
await list.add(download);
download.start();
@@ -189,12 +233,20 @@ add_task(async function test_download_content_analysis_user_cancels() {
// may or may not exist on disk.
}
await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function test_download_content_analysis_user_cancels() {
+ await check_download_content_analysis_user_cancels(false /* usePartFile */);
+});
+
+add_task(async function test_download_content_analysis_user_cancels_partfile() {
+ await check_download_content_analysis_user_cancels(true /* usePartFile */);
});
add_task(async function test_download_content_analysis_pref_defaults_to_off() {
- mockCA.setupForTest(false);
// do not set pref, so content analysis should not be consulted
- const download = await createTargetFileAndDownload();
+ mockCA.setupForTest(false);
+ const download = await createTargetFileAndDownload(false);
let list = await Downloads.getList(Downloads.PUBLIC);
await list.add(download);
await download.start();
@@ -202,6 +254,9 @@ add_task(async function test_download_content_analysis_pref_defaults_to_off() {
// Make sure the download succeeded.
ok(download.succeeded, "Download should succeed");
is(mockCA.calls.length, 0, "Content analysis should not be called");
- ok(await IOUtils.exists(download.target.path), "Target file should exist");
+ ok(
+ await IOUtils.exists(download.target.path),
+ "Final target file should exist"
+ );
await IOUtils.remove(download.target.path);
});
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_download_save_link_as_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_download_save_link_as_content_analysis.js
index 9ceef3b7088..4e9cd9815b3 100644
--- a/toolkit/components/contentanalysis/tests/browser/browser_download_save_link_as_content_analysis.js
+++ b/toolkit/components/contentanalysis/tests/browser/browser_download_save_link_as_content_analysis.js
@@ -23,27 +23,55 @@ async function triggerSaveAs({ selector }) {
contextMenu.activateItem(saveLinkCommand);
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js
index 0b578d8a5b5..bad2fa0a975 100644
--- a/toolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js
+++ b/toolkit/components/contentanalysis/tests/browser/browser_download_content_analysis.js
@@ -12,9 +12,9 @@ add_setup(async function test_setup() {
const DOWNLOAD_URL =
"https://example.com/browser/toolkit/components/contentanalysis/tests/browser/file_to_download.unknownextension";
-async function createTargetFileAndDownload() {
+async function createTargetFileAndDownload(usePartFile) {
// Create a temporary file that will be downloaded to.
- const targetFile = await IOUtils.createUniqueFile(
+ const targetFileName = await IOUtils.createUniqueFile(
PathUtils.tempDir,
"target_download_for_content_analysis.txt",
0o600
@@ -23,13 +23,16 @@ async function createTargetFileAndDownload() {
// successfully downloaded. Note that this theoretically introduces
// a race condition since something else could come in and create
// a temp file with the same name. This seems unlikely.
- await IOUtils.remove(targetFile, { ignoreAbsent: false });
+ await IOUtils.remove(targetFileName, { ignoreAbsent: false });
return await Downloads.createDownload({
source: {
url: DOWNLOAD_URL,
},
- target: targetFile,
+ target: {
+ path: targetFileName,
+ partFilePath: usePartFile ? targetFileName + ".part" : undefined,
+ },
});
}
@@ -90,7 +93,7 @@ function assertContentAnalysisDownloadRequest(request, expectedFilePath) {
ok(!!request.requestToken.length, "request requestToken should not be empty");
}
-add_task(async function test_download_content_analysis_allows() {
+async function check_download_content_analysis_allows(usePartFile) {
mockCA.setupForTest(true);
await SpecialPowers.pushPrefEnv({
set: [
@@ -98,7 +101,7 @@ add_task(async function test_download_content_analysis_allows() {
],
});
- const download = await createTargetFileAndDownload();
+ const download = await createTargetFileAndDownload(usePartFile);
let list = await Downloads.getList(Downloads.PUBLIC);
await list.add(download);
await download.start();
@@ -106,13 +109,33 @@ add_task(async function test_download_content_analysis_allows() {
// Make sure the download succeeded.
ok(download.succeeded, "Download should succeed");
is(mockCA.calls.length, 1, "Content analysis should be called once");
- assertContentAnalysisDownloadRequest(mockCA.calls[0], download.target.path);
- ok(await IOUtils.exists(download.target.path), "Target file should exist");
+ assertContentAnalysisDownloadRequest(
+ mockCA.calls[0],
+ usePartFile ? download.target.partFilePath : download.target.path
+ );
+ ok(
+ await IOUtils.exists(download.target.path),
+ "Final target file should exist"
+ );
+ if (usePartFile) {
+ ok(
+ !(await IOUtils.exists(download.target.partFilePath)),
+ "Part file should not still exist"
+ );
+ }
await IOUtils.remove(download.target.path);
await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function test_download_content_analysis_allows() {
+ await check_download_content_analysis_allows(false /* usePartFile */);
});
-add_task(async function test_download_content_analysis_blocks() {
+add_task(async function test_download_content_analysis_allows_partfile() {
+ await check_download_content_analysis_allows(true /* usePartFile */);
+});
+
+async function check_download_content_analysis_blocks(usePartFile) {
mockCA.setupForTest(false);
await SpecialPowers.pushPrefEnv({
set: [
@@ -120,28 +143,45 @@ add_task(async function test_download_content_analysis_blocks() {
],
});
- const download = await createTargetFileAndDownload();
- info(`path is ${download.target.path}`);
+ const download = await createTargetFileAndDownload(usePartFile);
+ if (usePartFile) {
+ info(`part file path is ${download.target.partFilePath}`);
+ }
+ info(`final path is ${download.target.path}`);
await Assert.rejects(
download.start(),
ex => ex instanceof Downloads.Error && ex.becauseBlockedByContentAnalysis,
"Download should have been rejected"
);
- info(`path is ${download.target.path}`);
ok(!download.succeeded, "Download should not succeed");
is(mockCA.calls.length, 1, "Content analysis should be called once");
- info(`path is ${download.target.path}`);
- assertContentAnalysisDownloadRequest(mockCA.calls[0], download.target.path);
- info(`path is ${download.target.path}`);
+ assertContentAnalysisDownloadRequest(
+ mockCA.calls[0],
+ usePartFile ? download.target.partFilePath : download.target.path
+ );
ok(
!(await IOUtils.exists(download.target.path)),
- "Target file should not exist"
+ "Final target file should not exist"
);
+ if (usePartFile) {
+ ok(
+ !(await IOUtils.exists(download.target.partFilePath)),
+ "Part file should not still exist"
+ );
+ }
await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function test_download_content_analysis_blocks() {
+ await check_download_content_analysis_blocks(false /* usePartFile */);
});
-add_task(async function test_download_content_analysis_user_cancels() {
+add_task(async function test_download_content_analysis_blocks_partfile() {
+ await check_download_content_analysis_blocks(true /* usePartFile */);
+});
+
+async function check_download_content_analysis_user_cancels(usePartFile) {
// Make the mock CA service wait for event so the test can
// cancel the download before the scan finishes.
mockCA.setupForTest(true, /* waitForEvent */ true);
@@ -156,9 +196,13 @@ add_task(async function test_download_content_analysis_user_cancels() {
once: true,
});
});
- const download = await createTargetFileAndDownload();
+ const download = await createTargetFileAndDownload(usePartFile);
+
+ if (usePartFile) {
+ info(`part file path is ${download.target.partFilePath}`);
+ }
+ info(`final path is ${download.target.path}`);
- info(`path is ${download.target.path}`);
let list = await Downloads.getList(Downloads.PUBLIC);
await list.add(download);
download.start();
@@ -189,12 +233,20 @@ add_task(async function test_download_content_analysis_user_cancels() {
// may or may not exist on disk.
}
await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function test_download_content_analysis_user_cancels() {
+ await check_download_content_analysis_user_cancels(false /* usePartFile */);
+});
+
+add_task(async function test_download_content_analysis_user_cancels_partfile() {
+ await check_download_content_analysis_user_cancels(true /* usePartFile */);
});
add_task(async function test_download_content_analysis_pref_defaults_to_off() {
- mockCA.setupForTest(false);
// do not set pref, so content analysis should not be consulted
- const download = await createTargetFileAndDownload();
+ mockCA.setupForTest(false);
+ const download = await createTargetFileAndDownload(false);
let list = await Downloads.getList(Downloads.PUBLIC);
await list.add(download);
await download.start();
@@ -202,6 +254,9 @@ add_task(async function test_download_content_analysis_pref_defaults_to_off() {
// Make sure the download succeeded.
ok(download.succeeded, "Download should succeed");
is(mockCA.calls.length, 0, "Content analysis should not be called");
- ok(await IOUtils.exists(download.target.path), "Target file should exist");
+ ok(
+ await IOUtils.exists(download.target.path),
+ "Final target file should exist"
+ );
await IOUtils.remove(download.target.path);
});
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_download_save_link_as_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_download_save_link_as_content_analysis.js
index 9ceef3b7088..4e9cd9815b3 100644
--- a/toolkit/components/contentanalysis/tests/browser/browser_download_save_link_as_content_analysis.js
+++ b/toolkit/components/contentanalysis/tests/browser/browser_download_save_link_as_content_analysis.js
@@ -23,27 +23,55 @@ async function triggerSaveAs({ selector }) {
contextMenu.activateItem(saveLinkCommand);
}
+let tempDir;
+
+// Final file path for download.
let destFilePath;
-add_setup(async () => {
- mockCA = await mockContentAnalysisService(mockCA);
-});
+// path to file that is a copy of the file sent to CA, so we can (async) check
+// that it is the same file.
+let copiedScannedFilePath;
-function setupMockFilePicker() {
- const tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+add_setup(async () => {
+ tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
tempDir.append("test-download-dir");
if (!tempDir.exists()) {
tempDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
}
+ registerCleanupFunction(function () {
+ if (tempDir.exists()) {
+ tempDir.remove(true);
+ }
+ });
+ let oldAnalyzeContentRequestPrivate = mockCA.analyzeContentRequestPrivate;
+ mockCA.analyzeContentRequestPrivate = function (
+ request,
+ autoAcknowledge,
+ callback
+ ) {
+ // Copy the file being scanned now because the original (a .part file) is
+ // renamed or removed once the download completes.
+ let requestFile = new FileUtils.File(request.filePath);
+ requestFile.copyTo(tempDir, "file.copy");
+ let tempDirClone = tempDir.clone();
+ tempDirClone.append("file.copy");
+ copiedScannedFilePath = tempDirClone.path;
+ oldAnalyzeContentRequestPrivate.call(
+ this,
+ request,
+ autoAcknowledge,
+ callback
+ );
+ };
+ mockCA = await mockContentAnalysisService(mockCA);
+});
+
+function setupMockFilePicker() {
let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init();
registerCleanupFunction(function () {
MockFilePicker.cleanup();
-
- if (tempDir.exists()) {
- tempDir.remove(true);
- }
});
MockFilePicker.displayDirectory = tempDir;
@@ -59,7 +87,7 @@ function setupMockFilePicker() {
};
}
-function assertContentAnalysisDownloadRequest(request, expectedFilePath) {
+async function assertContentAnalysisDownloadRequest(request) {
is(request.url.spec, DOWNLOAD_URL, "request has correct URL");
is(
request.analysisType,
@@ -76,7 +104,17 @@ function assertContentAnalysisDownloadRequest(request, expectedFilePath) {
Ci.nsIContentAnalysisRequest.eDownload,
"request has correct operationTypeForDisplay"
);
- is(request.filePath, expectedFilePath, "request filePath should match");
+ // Compare file contents, not file names, as the download name will be the
+ // temporary name assigned before content analysis approval.
+ let scannedBytes = await IOUtils.read(copiedScannedFilePath);
+ let expectedBytes = new Uint8Array(
+ await (await fetch(DOWNLOAD_URL)).arrayBuffer()
+ );
+ Assert.deepEqual(
+ scannedBytes,
+ expectedBytes,
+ "scanned file is download file"
+ );
ok(!request.textContent?.length, "request textContent should be empty");
is(
request.userActionRequestsCount,
@@ -109,8 +147,9 @@ add_task(async function test_download_content_analysis_download_save_as() {
);
}, "Wait for the file to be downloaded");
is(mockCA.calls.length, 1, "Content analysis should be called once");
- assertContentAnalysisDownloadRequest(mockCA.calls[0], destFilePath);
+ await assertContentAnalysisDownloadRequest(mockCA.calls[0]);
await IOUtils.remove(destFilePath);
+ await IOUtils.remove(copiedScannedFilePath);
... (truncated)
Loading diff…
References
On This Page