Chrome · Extensions › Extensions
CVE-2026-106416
Logic Error in Extensions (Extensions)
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
forchrome/browser/extensions/api/font_settings/font_settings_api.cc |
modified | |
FontSettingsEventRouterchrome/browser/extensions/api/font_settings/font_settings_api.cc |
modified | |
FontSettingsEventRouterchrome/browser/extensions/api/font_settings/font_settings_api.h |
modified | |
TESTchrome/browser/extensions/api/font_settings/font_settings_api_unittest.cc |
modified | |
ifchrome/test/data/extensions/api_test/font_settings/standard/test.js |
modified |
Files Changed
chrome/test/data/webui/settings/appearance_fonts_page_test.tschrome/browser/extensions/api/font_settings/font_settings_api.ccchrome/test/data/extensions/api_test/font_settings/standard/test.jschrome/browser/resources/settings/appearance_page/appearance_fonts_page.tschrome/browser/resources/settings/appearance_page/appearance_fonts_page.html.tschrome/browser/extensions/BUILD.gnchrome/browser/extensions/api/font_settings/font_settings_api.hchrome/browser/extensions/api/font_settings/font_settings_api_unittest.cc
Patch
From 4b57ab62e4777c52194ab730e6056893ced8fffc Mon Sep 17 00:00:00 2001 From: Eva Su <[email protected]> Date: Mon, 14 Sep 2026 13:57:05 -0700 Subject: [PATCH] [Extensions] Validate font ID format in fontSettings extension API Add input validation to FontSettingsSetFontFunction to ensure that font identifiers passed to chrome.fontSettings.setFont conform to expected structure and bounds before being stored in user preferences. Before this, chrome.fontSettings.setFont did not validate the fontId parameter before storing it in user preferences which can lead to malicious extensions injecting arbitrary CSS into the chrome://settings/fonts page. Introduce IsValidFontName to verify that font names are valid UTF-8, do not exceed 256 bytes, and contain only supported font naming characters. Calls with invalid font IDs now fail immediately with an error. Fixed: 514072462 Change-Id: I09c9afab707763eb52da86fe46305369185552fc Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8373778 Reviewed-by: Solomon Kinard <[email protected]> Reviewed-by: Mickey Burks <[email protected]> Reviewed-by: Darryl James <[email protected]> Commit-Queue: Eva Su <[email protected]> Cr-Commit-Position: refs/heads/main@{#1697530} --- diff --git a/chrome/browser/extensions/api/font_settings/font_settings_api.cc b/chrome/browser/extensions/api/font_settings/font_settings_api.cc index 1cb944162..d25b524 100644 --- a/chrome/browser/extensions/api/font_settings/font_settings_api.cc +++ b/chrome/browser/extensions/api/font_settings/font_settings_api.cc @@ -8,6 +8,7 @@ #include <stddef.h> +#include <string_view> #include <utility> #include "base/command_line.h" @@ -59,6 +60,7 @@ const char kSetFromIncognitoError[] = "Can't modify regular settings from an incognito context."; +const char kInvalidFontIdError[] = "Invalid font ID."; // Gets the font name preference path for |generic_family| and |script|. If // |script| is NULL, uses prefs::kWebKitCommonScript. @@ -103,6 +105,33 @@ } // namespace +bool IsValidFontName(std::string_view font_name) { + if (font_name.empty()) { + return true; + } + + if (font_name.size() > 256) { + return false; + } + + if (!base::IsStringUTF8(font_name)) { + return false; + } + + for (char c : font_name) { + if (static_cast<unsigned char>(c) >= 0x80) { + continue; + } + + if (!(base::IsAsciiAlphaNumeric(c) || c == ' ' || c == '-' || c == '_' || + c == '.' || c == '+')) { + return false; + } + } + + return true; +} + // This class observes pref changed events on a profile and dispatches the // corresponding extension API events to extensions. class FontSettingsEventRouter { @@ -328,6 +357,10 @@ fonts::SetFont::Params::Create(args()); EXTENSION_FUNCTION_VALIDATE(params); + if (!IsValidFontName(params->details.font_id)) { + return RespondNow(Error(kInvalidFontIdError)); + } + std::string pref_path = GetFontNamePrefPath(params->details.generic_family, params->details.script); diff --git a/chrome/browser/extensions/api/font_settings/font_settings_api.h b/chrome/browser/extensions/api/font_settings/font_settings_api.h index a2d0d5f..df4ef83 100644 --- a/chrome/browser/extensions/api/font_settings/font_settings_api.h +++ b/chrome/browser/extensions/api/font_settings/font_settings_api.h @@ -10,6 +10,7 @@ #include <memory> #include <string> +#include <string_view> #include "base/memory/raw_ptr.h" #include "base/values.h" @@ -25,6 +26,10 @@ namespace extensions { +// Returns true if the given font name contains only valid characters and valid +// UTF-8 encoding. +bool IsValidFontName(std::string_view font_name); + class FontSettingsEventRouter; // The profile-keyed service that manages the font_settings extension API. diff --git a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html.ts b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html.ts index 90c072bb..cd0a346 100644 --- a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html.ts +++ b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html.ts @@ -32,7 +32,7 @@ </settings-slider> <div id="minimumSizeFontPreview" style="font-size:${this.getMinimumFontSize_()}px; - font-family:'${this.standardFontPref_?.value || ''}';" + font-family:${this.computeFontFamilyStyle_(this.standardFontPref_?.value)};" ?hidden="${this.getMinimumSizeHidden_()}"> ${this.getMinimumFontSize_()}: $i18n{quickBrownFox} @@ -52,7 +52,7 @@ </div> <div id="standardFontPreview" class="list-item cr-padded-text" style="font-size:${this.defaultFontSizePref_?.value || 0}px; - font-family:'${this.standardFontPref_?.value || ''}';"> + font-family:${this.computeFontFamilyStyle_(this.standardFontPref_?.value)};"> ${this.defaultFontSizePref_?.value || 0}: $i18n{quickBrownFox} </div> @@ -70,7 +70,7 @@ </div> <div id="serifFontPreview" class="list-item cr-padded-text" style="font-size:${this.defaultFontSizePref_?.value || 0}px; - font-family:'${this.serifFontPref_?.value || ''}';"> + font-family:${this.computeFontFamilyStyle_(this.serifFontPref_?.value)};"> ${this.defaultFontSizePref_?.value || 0}: $i18n{quickBrownFox} </div> @@ -88,7 +88,7 @@ </div> <div id="sansSerifFontPreview" class="list-item cr-padded-text" style="font-size:${this.defaultFontSizePref_?.value || 0}px; - font-family:'${this.sansSerifFontPref_?.value || ''}';"> + font-family:${this.computeFontFamilyStyle_(this.sansSerifFontPref_?.value)};"> ${this.defaultFontSizePref_?.value || 0}: $i18n{quickBrownFox} </div> @@ -106,7 +106,7 @@ </div> <div id="fixedFontPreview" class="list-item cr-padded-text" style="font-size: ${this.defaultFixedFontSizePref_?.value || 0}px; - font-family: '${this.fontFamilyValueForFixed_()}';"> + font-family:${this.computeFontFamilyStyle_(this.fontFamilyValueForFixed_())};"> ${this.defaultFixedFontSizePref_?.value || 0}: $i18n{quickBrownFox} </div> @@ -129,7 +129,7 @@ layout constants. This is what the formula below tries to do. --> <div id="mathFontPreview" class="list-item cr-padded-text" style="font-size:${this.defaultFontSizePref_?.value || 0}px; - font-family:'${this.mathFontPref_?.value || ''}';"> + font-family:${this.computeFontFamilyStyle_(this.mathFontPref_?.value)};"> ${this.defaultFontSizePref_?.value || 0}: <math style="font: inherit;" displaystyle="true"> <mrow> diff --git a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.ts b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.ts index 049b4b8..94a0ff7c 100644 --- a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.ts +++ b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.ts @@ -161,6 +161,10 @@ return this.fixedFontPref_.value; } + protected computeFontFamilyStyle_(fontFamily?: string): string { + return fontFamily ? JSON.stringify(fontFamily) : 'inherit'; + } + // SettingsViewMixinLit implementation. override focusBackButton() { this.shadowRoot.querySelector('settings-subpage')!.focusBackButton(); diff --git a/chrome/browser/extensions/api/font_settings/font_settings_api_unittest.cc b/chrome/browser/extensions/api/font_settings/font_settings_api_unittest.cc new file mode 100644 index 0000000..45992e0 --- /dev/null +++ b/chrome/browser/extensions/api/font_settings/font_settings_api_unittest.cc @@ -0,0 +1,37 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/font_settings/font_settings_api.h" + +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +TEST(FontSettingsApiTest, FontNameValidation) { + EXPECT_TRUE(IsValidFontName("")); + EXPECT_TRUE(IsValidFontName("Arial")); + EXPECT_TRUE(IsValidFontName("Times New Roman")); + EXPECT_TRUE(IsValidFontName("Courier New")); + EXPECT_TRUE(IsValidFontName("sans-serif")); + EXPECT_TRUE(IsValidFontName("Osaka-Mono")); + EXPECT_TRUE(IsValidFontName("M+ 1p")); + EXPECT_TRUE(IsValidFontName("custom_standard")); + EXPECT_TRUE(IsValidFontName("宋体")); + + EXPECT_FALSE(IsValidFontName("x'; position: fixed;")); + EXPECT_FALSE(IsValidFontName("font;")); + EXPECT_FALSE(IsValidFontName("font\"")); + EXPECT_FALSE(IsValidFontName("font'")); + EXPECT_FALSE(IsValidFontName("font\\")); + EXPECT_FALSE(IsValidFontName("font{color:red}")); + EXPECT_FALSE(IsValidFontName("font:10px")); + EXPECT_FALSE(IsValidFontName("font/*comment*/")); + EXPECT_FALSE(IsValidFontName("font\n")); + EXPECT_FALSE(IsValidFontName("font, serif")); + EXPECT_FALSE(IsValidFontName(std::string(300, 'a'))); +} + +} // namespace extensions diff --git a/chrome/test/data/extensions/api_test/font_settings/standard/test.js b/chrome/test/data/extensions/api_test/font_settings/standard/test.js index c32aa22..c8c1f4b 100644 --- a/chrome/test/data/extensions/api_test/font_settings/standard/test.js +++ b/chrome/test/data/extensions/api_test/font_settings/standard/test.js @@ -268,4 +268,40 @@ fs.clearMinimumFontSize({}, chrome.test.callbackPass()); }, + + function setInvalidFontNames() { + const invalidNames = [ + 'invalid\'font;--x:', + 'test"font', + 'test{font}', + 'test<font>', + 'test\\font', + 'test/font', + 'test:font', + 'test;font', + 'test!font', + 'test@font', + 'test`font', + 'test=font', + 'test,font', + 'test\nfont', + 'test\rfont', + 'a'.repeat(300), + ]; + function testNext(index) { + if (index >= invalidNames.length) { + chrome.test.succeed(); + return; + } + fs.setFont( + { + genericFamily: 'standard', + fontId: invalidNames[index], + }, + chrome.test.callbackFail('Invalid font ID.', () => { + testNext(index + 1); + })); + } + testNext(0); + }, ]); diff --git a/chrome/test/data/webui/settings/appearance_fonts_page_test.ts b/chrome/test/data/webui/settings/appearance_fonts_page_test.ts index 75dc714b..5d6f3ef 100644 --- a/chrome/test/data/webui/settings/appearance_fonts_page_test.ts +++ b/chrome/test/data/webui/settings/appearance_fonts_page_test.ts @@ -241,4 +241,24 @@ `math preview contains element <${tagName}>`); }); }); + + test('font preview special characters', async () => { + // Verify that special characters in font names do not modify other CSS + // properties. + prefsBrowserProxy.fakeApi.sendPrefChanges([ + { + key: 'webkit.webprefs.fonts.standard.Zyyy', + value: 'sample\'; position: fixed; inset: 0; --custom:\'', + }, + ]); + await microtasksFinished(); + + const element = fontsPage.$.standardFontPreview; + assertFalse(element.style.position === 'fixed'); + assertEquals('', element.style.position); + assertEquals('', element.style.inset); + assertEquals( + `"sample'; position: fixed; inset: 0; --custom:'"`, + element.style.fontFamily); + }); }); diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn index ee0e5870..563383f 100644 --- a/chrome/browser/extensions/BUILD.gn +++ b/chrome/browser/extensions/BUILD.gn @@ -1923,6 +1923,7 @@ "api/developer_private/extension_info_generator_unittest.cc", "api/enterprise_hardware_platform/enterprise_hardware_platform_api_unittest.cc", "api/extension_action/extension_action_api_unittest.cc", + "api/font_settings/font_settings_api_unittest.cc", "api/gcm/extension_gcm_app_handler_unittest.cc", "api/identity/extension_token_key_unittest.cc", "api/identity/identity_api_unittest.cc",
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/extensions/api/font_settings/font_settings_api_unittest.cc b/chrome/browser/extensions/api/font_settings/font_settings_api_unittest.cc
new file mode 100644
index 0000000..45992e0
--- /dev/null
+++ b/chrome/browser/extensions/api/font_settings/font_settings_api_unittest.cc
@@ -0,0 +1,37 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/api/font_settings/font_settings_api.h"
+
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+TEST(FontSettingsApiTest, FontNameValidation) {
+ EXPECT_TRUE(IsValidFontName(""));
+ EXPECT_TRUE(IsValidFontName("Arial"));
+ EXPECT_TRUE(IsValidFontName("Times New Roman"));
+ EXPECT_TRUE(IsValidFontName("Courier New"));
+ EXPECT_TRUE(IsValidFontName("sans-serif"));
+ EXPECT_TRUE(IsValidFontName("Osaka-Mono"));
+ EXPECT_TRUE(IsValidFontName("M+ 1p"));
+ EXPECT_TRUE(IsValidFontName("custom_standard"));
+ EXPECT_TRUE(IsValidFontName("宋体"));
+
+ EXPECT_FALSE(IsValidFontName("x'; position: fixed;"));
+ EXPECT_FALSE(IsValidFontName("font;"));
+ EXPECT_FALSE(IsValidFontName("font\""));
+ EXPECT_FALSE(IsValidFontName("font'"));
+ EXPECT_FALSE(IsValidFontName("font\\"));
+ EXPECT_FALSE(IsValidFontName("font{color:red}"));
+ EXPECT_FALSE(IsValidFontName("font:10px"));
+ EXPECT_FALSE(IsValidFontName("font/*comment*/"));
+ EXPECT_FALSE(IsValidFontName("font\n"));
+ EXPECT_FALSE(IsValidFontName("font, serif"));
+ EXPECT_FALSE(IsValidFontName(std::string(300, 'a')));
+}
+
+} // namespace extensions
diff --git a/chrome/test/data/extensions/api_test/font_settings/standard/test.js b/chrome/test/data/extensions/api_test/font_settings/standard/test.js
index c32aa22..c8c1f4b 100644
--- a/chrome/test/data/extensions/api_test/font_settings/standard/test.js
+++ b/chrome/test/data/extensions/api_test/font_settings/standard/test.js
@@ -268,4 +268,40 @@
fs.clearMinimumFontSize({}, chrome.test.callbackPass());
},
+
+ function setInvalidFontNames() {
+ const invalidNames = [
+ 'invalid\'font;--x:',
+ 'test"font',
+ 'test{font}',
+ 'test<font>',
+ 'test\\font',
+ 'test/font',
+ 'test:font',
+ 'test;font',
+ 'test!font',
+ 'test@font',
+ 'test`font',
+ 'test=font',
+ 'test,font',
+ 'test\nfont',
+ 'test\rfont',
+ 'a'.repeat(300),
+ ];
+ function testNext(index) {
+ if (index >= invalidNames.length) {
+ chrome.test.succeed();
+ return;
+ }
+ fs.setFont(
+ {
+ genericFamily: 'standard',
+ fontId: invalidNames[index],
+ },
+ chrome.test.callbackFail('Invalid font ID.', () => {
+ testNext(index + 1);
+ }));
+ }
+ testNext(0);
+ },
]);
diff --git a/chrome/test/data/webui/settings/appearance_fonts_page_test.ts b/chrome/test/data/webui/settings/appearance_fonts_page_test.ts
index 75dc714b..5d6f3ef 100644
--- a/chrome/test/data/webui/settings/appearance_fonts_page_test.ts
+++ b/chrome/test/data/webui/settings/appearance_fonts_page_test.ts
@@ -241,4 +241,24 @@
`math preview contains element <${tagName}>`);
});
});
+
+ test('font preview special characters', async () => {
+ // Verify that special characters in font names do not modify other CSS
+ // properties.
+ prefsBrowserProxy.fakeApi.sendPrefChanges([
+ {
+ key: 'webkit.webprefs.fonts.standard.Zyyy',
+ value: 'sample\'; position: fixed; inset: 0; --custom:\'',
+ },
+ ]);
+ await microtasksFinished();
+
+ const element = fontsPage.$.standardFontPreview;
+ assertFalse(element.style.position === 'fixed');
+ assertEquals('', element.style.position);
+ assertEquals('', element.style.inset);
+ assertEquals(
+ `"sample'; position: fixed; inset: 0; --custom:'"`,
+ element.style.fontFamily);
+ });
});
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page