Skip to content

Commit b783e00

Browse files
committed
Bug 1977201: Add read access to user font files not in the Windows user font dir. r=handyman
This is for USER_LIMITED and files in the User's directory, because the GPU process loses access otherwise. These can be used because of combo box layout happening in the parent process. Differential Revision: https://phabricator.services.mozilla.com/D257715
1 parent f62f718 commit b783e00

File tree

7 files changed

+675
-16
lines changed

7 files changed

+675
-16
lines changed

security/sandbox/moz.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ elif CONFIG["OS_ARCH"] == "WINNT":
3131
"win/src/sandboxtarget",
3232
]
3333

34+
TEST_DIRS += [
35+
"win/gtest",
36+
]
37+
3438
EXPORTS.mozilla.sandboxing += [
3539
"chromium-shim/sandbox/win/loggingCallbacks.h",
3640
"chromium-shim/sandbox/win/loggingTypes.h",
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2+
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3+
/* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
5+
* You can obtain one at http://mozilla.org/MPL/2.0/. */
6+
7+
#include "gtest/gtest.h"
8+
#include "gmock/gmock.h"
9+
10+
#include <string>
11+
#include <winreg.h>
12+
13+
#include "nsLiteralString.h"
14+
#include "sandbox/win/src/sandbox.h"
15+
#include "sandbox/win/src/app_container.h"
16+
#include "sandbox/win/src/policy_engine_opcodes.h"
17+
#include "../src/sandboxbroker/ConfigHelpers.h"
18+
19+
using namespace sandbox;
20+
using mozilla::sandboxing::UserFontConfigHelper;
21+
using ::testing::Eq;
22+
using ::testing::Expectation;
23+
using ::testing::StartsWith;
24+
using ::testing::StrEq;
25+
using ::testing::StrictMock;
26+
27+
static const nsLiteralString sWinUserProfile = uR"(C:\Users\Moz User)"_ns;
28+
static const nsLiteralString sLocalAppData =
29+
uR"(C:\Users\Moz User\AppData\Local)"_ns;
30+
static const wchar_t* sWinUserFonts =
31+
LR"(C:\Users\Moz User\AppData\Local\Microsoft\Windows\Fonts\*)";
32+
static const wchar_t* sTestRegKey = LR"(Software\MozFontsPathsTest)";
33+
static const wchar_t* sTestFailRegKey = LR"(Software\MozFontsPathsTestFail)";
34+
35+
namespace mozilla {
36+
37+
class MockConfig : public TargetConfig {
38+
public:
39+
MOCK_METHOD(ResultCode, AllowFileAccess,
40+
(FileSemantics semantics, const wchar_t* pattern), (override));
41+
42+
// Remaining methods should not be called during tests.
43+
MOCK_METHOD(bool, IsConfigured, (), (const, override));
44+
MOCK_METHOD(ResultCode, SetTokenLevel,
45+
(TokenLevel initial, TokenLevel lockdown), (override));
46+
MOCK_METHOD(TokenLevel, GetInitialTokenLevel, (), (const, override));
47+
MOCK_METHOD(TokenLevel, GetLockdownTokenLevel, (), (const, override));
48+
MOCK_METHOD(void, SetDoNotUseRestrictingSIDs, (), (override));
49+
MOCK_METHOD(bool, GetUseRestrictingSIDs, (), (override));
50+
MOCK_METHOD(void, SetForceKnownDllLoadingFallback, (), (override));
51+
MOCK_METHOD(ResultCode, SetJobLevel,
52+
(JobLevel job_level, uint32_t ui_exceptions), (override));
53+
MOCK_METHOD(JobLevel, GetJobLevel, (), (const, override));
54+
MOCK_METHOD(void, SetJobMemoryLimit, (size_t memory_limit), (override));
55+
MOCK_METHOD(ResultCode, AllowNamedPipes, (const wchar_t* pattern),
56+
(override));
57+
MOCK_METHOD(ResultCode, AllowRegistryRead, (const wchar_t* pattern),
58+
(override));
59+
MOCK_METHOD(ResultCode, AllowExtraDlls, (const wchar_t* pattern), (override));
60+
MOCK_METHOD(ResultCode, SetFakeGdiInit, (), (override));
61+
MOCK_METHOD(ResultCode, AllowLineBreaking, (), (override));
62+
MOCK_METHOD(void, AddDllToUnload, (const wchar_t* dll_name), (override));
63+
MOCK_METHOD(ResultCode, SetIntegrityLevel, (IntegrityLevel level),
64+
(override));
65+
MOCK_METHOD(IntegrityLevel, GetIntegrityLevel, (), (const, override));
66+
MOCK_METHOD(void, SetDelayedIntegrityLevel, (IntegrityLevel level),
67+
(override));
68+
MOCK_METHOD(ResultCode, SetLowBox, (const wchar_t* sid), (override));
69+
MOCK_METHOD(ResultCode, SetProcessMitigations, (MitigationFlags flags),
70+
(override));
71+
MOCK_METHOD(MitigationFlags, GetProcessMitigations, (), (override));
72+
MOCK_METHOD(ResultCode, SetDelayedProcessMitigations, (MitigationFlags flags),
73+
(override));
74+
MOCK_METHOD(MitigationFlags, GetDelayedProcessMitigations, (),
75+
(const, override));
76+
MOCK_METHOD(void, AddRestrictingRandomSid, (), (override));
77+
MOCK_METHOD(void, SetLockdownDefaultDacl, (), (override));
78+
MOCK_METHOD(ResultCode, AddAppContainerProfile,
79+
(const wchar_t* package_name, bool create_profile), (override));
80+
MOCK_METHOD(scoped_refptr<AppContainer>, GetAppContainer, (), (override));
81+
MOCK_METHOD(ResultCode, AddKernelObjectToClose,
82+
(const wchar_t* handle_type, const wchar_t* handle_name),
83+
(override));
84+
MOCK_METHOD(ResultCode, SetDisconnectCsrss, (), (override));
85+
MOCK_METHOD(void, SetDesktop, (Desktop desktop), (override));
86+
MOCK_METHOD(void, SetFilterEnvironment, (bool filter), (override));
87+
MOCK_METHOD(bool, GetEnvironmentFiltered, (), (override));
88+
MOCK_METHOD(void, SetZeroAppShim, (), (override));
89+
};
90+
91+
#define EXPECT_READONLY_EQ(aRulePath) \
92+
EXPECT_CALL(mConfig, AllowFileAccess(Eq(FileSemantics::kAllowReadonly), \
93+
StrEq(aRulePath)))
94+
95+
#define EXPECT_READONLY_STARTS(aRulePath) \
96+
EXPECT_CALL(mConfig, AllowFileAccess(Eq(FileSemantics::kAllowReadonly), \
97+
StartsWith(aRulePath)))
98+
99+
class UserFontConfigHelperTest : public testing::Test {
100+
protected:
101+
// We always expect the Windows User font dir rule to be added.
102+
UserFontConfigHelperTest()
103+
: mWinUserFontCall(EXPECT_READONLY_EQ(sWinUserFonts)) {
104+
::RegCreateKeyExW(HKEY_CURRENT_USER, sTestRegKey, 0, nullptr,
105+
REG_OPTION_VOLATILE, KEY_ALL_ACCESS, nullptr,
106+
&mTestUserFontKey, nullptr);
107+
}
108+
109+
~UserFontConfigHelperTest() {
110+
if (mTestUserFontKey) {
111+
::RegCloseKey(mTestUserFontKey);
112+
}
113+
::RegDeleteKeyW(HKEY_CURRENT_USER, sTestRegKey);
114+
}
115+
116+
void SetUpPaths(const std::vector<std::wstring_view>& aFontPaths) {
117+
for (size_t i = 0; i < aFontPaths.size(); ++i) {
118+
const auto* pathBytes =
119+
reinterpret_cast<const BYTE*>(aFontPaths[i].data());
120+
size_t sizeInBytes = (aFontPaths[i].length() + 1) * sizeof(wchar_t);
121+
::RegSetValueExW(mTestUserFontKey, std::to_wstring(i).c_str(), 0, REG_SZ,
122+
pathBytes, sizeInBytes);
123+
}
124+
}
125+
126+
void CreateHelperAndCallAddRules() {
127+
UserFontConfigHelper policyHelper(sTestRegKey, sWinUserProfile,
128+
sLocalAppData);
129+
// Only allow one page to test
130+
sandboxing::SizeTrackingConfig trackingPolicy(&mConfig, 1);
131+
policyHelper.AddRules(trackingPolicy);
132+
}
133+
134+
// StrictMock because we only expect AllowFileAccess to be called.
135+
StrictMock<MockConfig> mConfig;
136+
const Expectation mWinUserFontCall;
137+
HKEY mTestUserFontKey = nullptr;
138+
};
139+
140+
TEST_F(UserFontConfigHelperTest, WindowsDirRProgramDatauleAddedOnKeyFailure) {
141+
// Create helper with incorrect key name.
142+
UserFontConfigHelper policyHelper(sTestFailRegKey, sWinUserProfile,
143+
sLocalAppData);
144+
sandboxing::SizeTrackingConfig trackingPolicy(&mConfig, 1);
145+
policyHelper.AddRules(trackingPolicy);
146+
}
147+
148+
TEST_F(UserFontConfigHelperTest, PathsInsideUsersDirAdded) {
149+
SetUpPaths({LR"(C:\Users\Moz User\Fonts\FontFile1.ttf)"});
150+
151+
// We expect the windows user font rule to be added first.
152+
EXPECT_READONLY_EQ(LR"(C:\Users\Moz User\Fonts\FontFile1.ttf)")
153+
.After(mWinUserFontCall);
154+
155+
CreateHelperAndCallAddRules();
156+
}
157+
158+
TEST_F(UserFontConfigHelperTest, PathsInsideUsersDirAddedIgnoringCase) {
159+
SetUpPaths({LR"(C:\users\moz uSER\Fonts\FontFile1.ttf)"});
160+
161+
EXPECT_READONLY_EQ(LR"(C:\users\moz uSER\Fonts\FontFile1.ttf)")
162+
.After(mWinUserFontCall);
163+
164+
CreateHelperAndCallAddRules();
165+
}
166+
167+
TEST_F(UserFontConfigHelperTest, PathsOutsideUsersDirNotAdded) {
168+
SetUpPaths({LR"(C:\ProgramData\Fonts\FontFile1.ttf)",
169+
LR"(C:\programdata\Fonts\FontFile2.ttf)"});
170+
171+
EXPECT_READONLY_EQ(LR"(C:\ProgramData\Fonts\FontFile1.ttf)").Times(0);
172+
EXPECT_READONLY_EQ(LR"(C:\programdata\Fonts\FontFile2.ttf)").Times(0);
173+
174+
CreateHelperAndCallAddRules();
175+
}
176+
177+
TEST_F(UserFontConfigHelperTest, MultipleFontsInAndOutside) {
178+
SetUpPaths({
179+
LR"(C:\Users\Moz User\Fonts\FontFile1.ttf)",
180+
LR"(C:\Users\Moz User\Fonts\FontFile2.ttf)",
181+
LR"(C:\Users\Moz User\Fonts\FontFile3.ttf)",
182+
LR"(C:\ProgramData\Fonts\FontFile1.ttf)",
183+
LR"(C:\ProgramData\Fonts\FontFile2.ttf)",
184+
});
185+
186+
EXPECT_READONLY_EQ(LR"(C:\Users\Moz User\Fonts\FontFile1.ttf)")
187+
.After(mWinUserFontCall);
188+
EXPECT_READONLY_EQ(LR"(C:\Users\Moz User\Fonts\FontFile2.ttf)")
189+
.After(mWinUserFontCall);
190+
EXPECT_READONLY_EQ(LR"(C:\Users\Moz User\Fonts\FontFile3.ttf)")
191+
.After(mWinUserFontCall);
192+
EXPECT_READONLY_EQ(LR"(C:\ProgramData\Fonts\FontFile1.ttf)").Times(0);
193+
EXPECT_READONLY_EQ(LR"(C:\ProgramData\Fonts\FontFile2.ttf)").Times(0);
194+
195+
CreateHelperAndCallAddRules();
196+
}
197+
198+
TEST_F(UserFontConfigHelperTest, NonStringValueIsIgnored) {
199+
DWORD regValue = 42;
200+
::RegSetValueExW(mTestUserFontKey, L"Liff", 0, REG_DWORD,
201+
reinterpret_cast<const BYTE*>(&regValue), sizeof(regValue));
202+
wchar_t multiPath[] = LR"(C:\Users\Moz User\Fonts\FontFile1.ttf)";
203+
::RegSetValueExW(mTestUserFontKey, L"MultiStr", 0, REG_MULTI_SZ,
204+
reinterpret_cast<const BYTE*>(multiPath), sizeof(multiPath));
205+
::RegSetValueExW(mTestUserFontKey, L"ExpandStr", 0, REG_EXPAND_SZ,
206+
reinterpret_cast<const BYTE*>(multiPath), sizeof(multiPath));
207+
208+
EXPECT_READONLY_EQ(LR"(C:\Users\Moz User\Fonts\FontFile1.ttf)").Times(0);
209+
210+
CreateHelperAndCallAddRules();
211+
}
212+
213+
TEST_F(UserFontConfigHelperTest, PathNotNullTerminated) {
214+
// If you just miss the null in the size it stills gets stored with it, so you
215+
// have to make the next character non-null.
216+
wchar_t fontPath[] = LR"(C:\Users\Moz User\Fonts\FontFile1.ttfx)";
217+
::RegSetValueExW(mTestUserFontKey, L"NoNull", 0, REG_SZ,
218+
reinterpret_cast<const BYTE*>(fontPath),
219+
sizeof(fontPath) - (2 * sizeof(wchar_t)));
220+
221+
EXPECT_READONLY_EQ(LR"(C:\Users\Moz User\Fonts\FontFile1.ttf)")
222+
.After(mWinUserFontCall);
223+
224+
CreateHelperAndCallAddRules();
225+
}
226+
227+
TEST_F(UserFontConfigHelperTest, PathEmpty) {
228+
wchar_t fontPath[] = L"";
229+
::RegSetValueExW(mTestUserFontKey, L"EmptyNoNull", 0, REG_SZ,
230+
reinterpret_cast<const BYTE*>(fontPath), sizeof(fontPath));
231+
232+
EXPECT_READONLY_EQ(fontPath).Times(0);
233+
234+
CreateHelperAndCallAddRules();
235+
}
236+
237+
TEST_F(UserFontConfigHelperTest, PathEmptyNotNullTerminated) {
238+
// If you just miss the null in the size it stills gets stored with it, so you
239+
// have to make the next character non-null.
240+
wchar_t fontPath[] = L"F";
241+
::RegSetValueExW(mTestUserFontKey, L"EmptyNoNull", 0, REG_SZ,
242+
reinterpret_cast<const BYTE*>(fontPath), 0);
243+
244+
EXPECT_READONLY_EQ(L"").Times(0);
245+
246+
CreateHelperAndCallAddRules();
247+
}
248+
249+
TEST_F(UserFontConfigHelperTest, DirsAreIgnored) {
250+
SetUpPaths({LR"(C:\Users\Moz User\Fonts\)"});
251+
252+
EXPECT_READONLY_EQ(LR"(C:\Users\Moz Us]er\Fonts\)").Times(0);
253+
254+
CreateHelperAndCallAddRules();
255+
}
256+
257+
TEST_F(UserFontConfigHelperTest, PathsInWindowsUsersFontDirNotAdded) {
258+
SetUpPaths({
259+
LR"(C:\Users\Moz User\AppData\Local\Microsoft\Windows\Fonts\FontFile1.ttf)",
260+
LR"(C:\Users\Moz User\AppData\Local\Microsoft\Windows\Fonts\Sub\FontFile2.ttf)",
261+
});
262+
263+
EXPECT_READONLY_EQ(
264+
LR"(C:\Users\Moz User\AppData\Local\Microsoft\Windows\Fonts\FontFile1.ttf)")
265+
.Times(0);
266+
EXPECT_READONLY_EQ(
267+
LR"(C:\Users\Moz User\AppData\Local\Microsoft\Windows\Fonts\Sub\FontFile2.ttf)")
268+
.Times(0);
269+
270+
CreateHelperAndCallAddRules();
271+
}
272+
273+
TEST_F(UserFontConfigHelperTest,
274+
PathsInWindowsUsersFontDirNotAddedIgnoringCase) {
275+
SetUpPaths({
276+
LR"(c:\Users\mOZ user\aPPdATA\Local\microsoft\wINDows\Fonts\FontFile1.ttf)",
277+
LR"(c:\uSERS\moz user\aPPdATA\lOCAL\MICRosoft\WindOWS\fONTS\Sub\FontFile2.ttf)",
278+
});
279+
280+
EXPECT_READONLY_EQ(
281+
LR"(c:\Users\mOZ user\aPPdATA\Local\microsoft\wINDows\Fonts\FontFile1.ttf)")
282+
.Times(0);
283+
EXPECT_READONLY_EQ(
284+
LR"(c:\uSERS\moz user\aPPdATA\lOCAL\MICRosoft\WindOWS\fONTS\Sub\FontFile2.ttf)")
285+
.Times(0);
286+
287+
CreateHelperAndCallAddRules();
288+
}
289+
290+
std::wstring MakeLongFontPath(const wchar_t* aPrefix, const wchar_t* aSuffix) {
291+
static size_t sReqPathLen = []() {
292+
// Bytes taken up by the Windows user font path rule.
293+
size_t winUserFontSpace =
294+
(12 * sizeof(sandbox::PolicyOpcode)) +
295+
((wcslen(sWinUserFonts) + 4) * sizeof(wchar_t) * 4);
296+
297+
// The test fixture allows for one page.
298+
size_t remainingSpace = 4096 - winUserFontSpace;
299+
300+
// We want 3 paths to be too big, so divide by 3 and reverse the formula.
301+
size_t spacePerFontPath = remainingSpace / 3;
302+
size_t reqFontLen =
303+
((spacePerFontPath - (12 * sizeof(sandbox::PolicyOpcode))) /
304+
(4 * sizeof(wchar_t))) -
305+
4;
306+
307+
// Add on 1 to make it too long for 3 paths.
308+
return reqFontLen + 1;
309+
}();
310+
311+
std::wstring longFontPath(aPrefix);
312+
longFontPath.append(sReqPathLen - longFontPath.length() - wcslen(aSuffix),
313+
'F');
314+
longFontPath.append(aSuffix);
315+
return longFontPath;
316+
}
317+
318+
TEST_F(UserFontConfigHelperTest, PathsTooLongForStorage) {
319+
// These font paths take up enough storage such that, with the Windows user
320+
// font dir rule, only two will fit in the available 4K of storage. Note that
321+
// we can't guarantee the order they are returned from the registry.
322+
auto path1 = MakeLongFontPath(LR"(C:\Users\Moz User\)", L"1");
323+
auto path2 = MakeLongFontPath(LR"(C:\Users\Moz User\)", L"2");
324+
auto path3 = MakeLongFontPath(LR"(C:\Users\Moz User\)", L"3");
325+
SetUpPaths({
326+
path1,
327+
path2,
328+
path3,
329+
});
330+
331+
path1.pop_back();
332+
EXPECT_READONLY_STARTS(path1).Times(2).After(mWinUserFontCall);
333+
334+
CreateHelperAndCallAddRules();
335+
}
336+
337+
TEST_F(UserFontConfigHelperTest, PathsTooLongOneOutsideUserProfile) {
338+
// These font paths take up enough storage such that, with the Windows user
339+
// font dir rule, only two will fit in the available 4K of storage.
340+
// However one is outside the user profile, so we can be certain about which
341+
// rules should be added.
342+
auto path1 = MakeLongFontPath(LR"(C:\ProgramData\)", L"1");
343+
auto path2 = MakeLongFontPath(LR"(C:\Users\Moz User\)", L"2");
344+
auto path3 = MakeLongFontPath(LR"(C:\Users\Moz User\)", L"3");
345+
SetUpPaths({
346+
path1,
347+
path2,
348+
path3,
349+
});
350+
351+
EXPECT_READONLY_EQ(path1).Times(0);
352+
EXPECT_READONLY_EQ(path2).After(mWinUserFontCall);
353+
EXPECT_READONLY_EQ(path3).After(mWinUserFontCall);
354+
355+
CreateHelperAndCallAddRules();
356+
}
357+
358+
} // namespace mozilla
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
2+
# vim: set filetype=python:
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
7+
Library("sandboxtest")
8+
9+
UNIFIED_SOURCES = [
10+
"TestConfigHelpers.cpp",
11+
]
12+
13+
for var in ("UNICODE", "_UNICODE"):
14+
DEFINES[var] = True
15+
16+
LOCAL_INCLUDES += ["/security/sandbox/chromium-shim"]
17+
LOCAL_INCLUDES += ["/security/sandbox/chromium"]
18+
LOCAL_INCLUDES += ["/third_party/abseil-cpp"]
19+
20+
FINAL_LIBRARY = "xul-gtest"

0 commit comments

Comments
 (0)