/src/mozilla-central/xpcom/io/FilePreferences.cpp
Line | Count | Source (jump to first uncovered line) |
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 |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "FilePreferences.h" |
8 | | |
9 | | #include "mozilla/ClearOnShutdown.h" |
10 | | #include "mozilla/Preferences.h" |
11 | | #include "mozilla/StaticPtr.h" |
12 | | #include "mozilla/Tokenizer.h" |
13 | | #include "nsAppDirectoryServiceDefs.h" |
14 | | #include "nsDirectoryServiceDefs.h" |
15 | | #include "nsDirectoryServiceUtils.h" |
16 | | #include "nsString.h" |
17 | | |
18 | | namespace mozilla { |
19 | | namespace FilePreferences { |
20 | | |
21 | | static bool sBlockUNCPaths = false; |
22 | | typedef nsTArray<nsString> WinPaths; |
23 | | |
24 | | static WinPaths& PathWhitelist() |
25 | 0 | { |
26 | 0 | static WinPaths sPaths; |
27 | 0 | return sPaths; |
28 | 0 | } |
29 | | |
30 | | #ifdef XP_WIN |
31 | | typedef char16_t char_path_t; |
32 | | #else |
33 | | typedef char char_path_t; |
34 | | #endif |
35 | | |
36 | | typedef nsTArray<nsTString<char_path_t>> Paths; |
37 | | static StaticAutoPtr<Paths> sBlacklist; |
38 | | |
39 | | static Paths& PathBlacklist() |
40 | 0 | { |
41 | 0 | if (!sBlacklist) { |
42 | 0 | sBlacklist = new nsTArray<nsTString<char_path_t>>(); |
43 | 0 | ClearOnShutdown(&sBlacklist); |
44 | 0 | } |
45 | 0 | return *sBlacklist; |
46 | 0 | } |
47 | | |
48 | | static void AllowUNCDirectory(char const* directory) |
49 | 0 | { |
50 | 0 | nsCOMPtr<nsIFile> file; |
51 | 0 | NS_GetSpecialDirectory(directory, getter_AddRefs(file)); |
52 | 0 | if (!file) { |
53 | 0 | return; |
54 | 0 | } |
55 | 0 | |
56 | 0 | nsString path; |
57 | 0 | if (NS_FAILED(file->GetTarget(path))) { |
58 | 0 | return; |
59 | 0 | } |
60 | 0 | |
61 | 0 | // The whitelist makes sense only for UNC paths, because this code is used |
62 | 0 | // to block only UNC paths, hence, no need to add non-UNC directories here |
63 | 0 | // as those would never pass the check. |
64 | 0 | if (!StringBeginsWith(path, NS_LITERAL_STRING("\\\\"))) { |
65 | 0 | return; |
66 | 0 | } |
67 | 0 | |
68 | 0 | if (!PathWhitelist().Contains(path)) { |
69 | 0 | PathWhitelist().AppendElement(path); |
70 | 0 | } |
71 | 0 | } |
72 | | |
73 | | void InitPrefs() |
74 | 0 | { |
75 | 0 | sBlockUNCPaths = Preferences::GetBool("network.file.disable_unc_paths", false); |
76 | 0 |
|
77 | 0 | PathBlacklist().Clear(); |
78 | 0 | nsTAutoString<char_path_t> blacklist; |
79 | | #ifdef XP_WIN |
80 | | Preferences::GetString("network.file.path_blacklist", blacklist); |
81 | | #else |
82 | | Preferences::GetCString("network.file.path_blacklist", blacklist); |
83 | 0 | #endif |
84 | 0 |
|
85 | 0 | TTokenizer<char_path_t> p(blacklist); |
86 | 0 | while (!p.CheckEOF()) { |
87 | 0 | nsTString<char_path_t> path; |
88 | 0 | Unused << p.ReadUntil(TTokenizer<char_path_t>::Token::Char(','), path); |
89 | 0 | path.Trim(" "); |
90 | 0 | if (!path.IsEmpty()) { |
91 | 0 | PathBlacklist().AppendElement(path); |
92 | 0 | } |
93 | 0 | Unused << p.CheckChar(','); |
94 | 0 | } |
95 | 0 | } |
96 | | |
97 | | void InitDirectoriesWhitelist() |
98 | 0 | { |
99 | 0 | // NS_GRE_DIR is the installation path where the binary resides. |
100 | 0 | AllowUNCDirectory(NS_GRE_DIR); |
101 | 0 | // NS_APP_USER_PROFILE_50_DIR and NS_APP_USER_PROFILE_LOCAL_50_DIR are the two |
102 | 0 | // parts of the profile we store permanent and local-specific data. |
103 | 0 | AllowUNCDirectory(NS_APP_USER_PROFILE_50_DIR); |
104 | 0 | AllowUNCDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR); |
105 | 0 | } |
106 | | |
107 | | namespace { // anon |
108 | | |
109 | | template <typename TChar> |
110 | | class TNormalizer |
111 | | : public TTokenizer<TChar> |
112 | | { |
113 | | typedef TTokenizer<TChar> base; |
114 | | public: |
115 | | typedef typename base::Token Token; |
116 | | |
117 | | TNormalizer(const nsTSubstring<TChar>& aFilePath, const Token& aSeparator) |
118 | | : TTokenizer<TChar>(aFilePath) |
119 | | , mSeparator(aSeparator) |
120 | 0 | { |
121 | 0 | } Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char16_t>::TNormalizer(nsTSubstring<char16_t> const&, mozilla::TokenizerBase<char16_t>::Token const&) Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char>::TNormalizer(nsTSubstring<char> const&, mozilla::TokenizerBase<char>::Token const&) |
122 | | |
123 | | bool Get(nsTSubstring<TChar>& aNormalizedFilePath) |
124 | 0 | { |
125 | 0 | aNormalizedFilePath.Truncate(); |
126 | 0 |
|
127 | 0 | // Windows UNC paths begin with double separator (\\) |
128 | 0 | // Linux paths begin with just one separator (/) |
129 | 0 | // If we want to use the normalizer for regular windows paths this code |
130 | 0 | // will need to be updated. |
131 | | #ifdef XP_WIN |
132 | | if (base::Check(mSeparator)) { |
133 | | aNormalizedFilePath.Append(mSeparator.AsChar()); |
134 | | } |
135 | | #endif |
136 | |
|
137 | 0 | if (base::Check(mSeparator)) { |
138 | 0 | aNormalizedFilePath.Append(mSeparator.AsChar()); |
139 | 0 | } |
140 | 0 |
|
141 | 0 | while (base::HasInput()) { |
142 | 0 | if (!ConsumeName()) { |
143 | 0 | return false; |
144 | 0 | } |
145 | 0 | } |
146 | 0 |
|
147 | 0 | for (auto const& name : mStack) { |
148 | 0 | aNormalizedFilePath.Append(name); |
149 | 0 | } |
150 | 0 |
|
151 | 0 | return true; |
152 | 0 | } Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char16_t>::Get(nsTSubstring<char16_t>&) Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char>::Get(nsTSubstring<char>&) |
153 | | |
154 | | |
155 | | private: |
156 | | bool ConsumeName() |
157 | 0 | { |
158 | 0 | if (base::CheckEOF()) { |
159 | 0 | return true; |
160 | 0 | } |
161 | 0 | |
162 | 0 | if (CheckCurrentDir()) { |
163 | 0 | return true; |
164 | 0 | } |
165 | 0 | |
166 | 0 | if (CheckParentDir()) { |
167 | 0 | if (!mStack.Length()) { |
168 | 0 | // This means there are more \.. than valid names |
169 | 0 | return false; |
170 | 0 | } |
171 | 0 | |
172 | 0 | mStack.RemoveLastElement(); |
173 | 0 | return true; |
174 | 0 | } |
175 | 0 | |
176 | 0 | nsTDependentSubstring<TChar> name; |
177 | 0 | if (base::ReadUntil(mSeparator, name, base::INCLUDE_LAST) && name.Length() == 1) { |
178 | 0 | // this means and empty name (a lone slash), which is illegal |
179 | 0 | return false; |
180 | 0 | } |
181 | 0 | mStack.AppendElement(name); |
182 | 0 |
|
183 | 0 | return true; |
184 | 0 | } Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char16_t>::ConsumeName() Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char>::ConsumeName() |
185 | | |
186 | | bool CheckParentDir() |
187 | 0 | { |
188 | 0 | typename nsTString<TChar>::const_char_iterator cursor = base::mCursor; |
189 | 0 | if (base::CheckChar('.') && base::CheckChar('.') && CheckSeparator()) { |
190 | 0 | return true; |
191 | 0 | } |
192 | 0 | |
193 | 0 | base::mCursor = cursor; |
194 | 0 | return false; |
195 | 0 | } Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char16_t>::CheckParentDir() Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char>::CheckParentDir() |
196 | | |
197 | | bool CheckCurrentDir() |
198 | 0 | { |
199 | 0 | typename nsTString<TChar>::const_char_iterator cursor = base::mCursor; |
200 | 0 | if (base::CheckChar('.') && CheckSeparator()) { |
201 | 0 | return true; |
202 | 0 | } |
203 | 0 | |
204 | 0 | base::mCursor = cursor; |
205 | 0 | return false; |
206 | 0 | } Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char16_t>::CheckCurrentDir() Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char>::CheckCurrentDir() |
207 | | |
208 | | bool CheckSeparator() |
209 | 0 | { |
210 | 0 | return base::Check(mSeparator) || base::CheckEOF(); |
211 | 0 | } Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char16_t>::CheckSeparator() Unexecuted instantiation: Unified_cpp_xpcom_io0.cpp:mozilla::FilePreferences::(anonymous namespace)::TNormalizer<char>::CheckSeparator() |
212 | | |
213 | | Token const mSeparator; |
214 | | nsTArray<nsTDependentSubstring<TChar>> mStack; |
215 | | }; |
216 | | |
217 | | } // anon |
218 | | |
219 | | bool IsBlockedUNCPath(const nsAString& aFilePath) |
220 | 0 | { |
221 | 0 | typedef TNormalizer<char16_t> Normalizer; |
222 | 0 | if (!sBlockUNCPaths) { |
223 | 0 | return false; |
224 | 0 | } |
225 | 0 | |
226 | 0 | if (!StringBeginsWith(aFilePath, NS_LITERAL_STRING("\\\\"))) { |
227 | 0 | return false; |
228 | 0 | } |
229 | 0 | |
230 | 0 | nsAutoString normalized; |
231 | 0 | if (!Normalizer(aFilePath, Normalizer::Token::Char('\\')).Get(normalized)) { |
232 | 0 | // Broken paths are considered invalid and thus inaccessible |
233 | 0 | return true; |
234 | 0 | } |
235 | 0 | |
236 | 0 | for (const auto& allowedPrefix : PathWhitelist()) { |
237 | 0 | if (StringBeginsWith(normalized, allowedPrefix)) { |
238 | 0 | if (normalized.Length() == allowedPrefix.Length()) { |
239 | 0 | return false; |
240 | 0 | } |
241 | 0 | if (normalized[allowedPrefix.Length()] == L'\\') { |
242 | 0 | return false; |
243 | 0 | } |
244 | 0 | |
245 | 0 | // When we are here, the path has a form "\\path\prefixevil" |
246 | 0 | // while we have an allowed prefix of "\\path\prefix". |
247 | 0 | // Note that we don't want to add a slash to the end of a prefix |
248 | 0 | // so that opening the directory (no slash at the end) still works. |
249 | 0 | break; |
250 | 0 | } |
251 | 0 | } |
252 | 0 |
|
253 | 0 | return true; |
254 | 0 | } |
255 | | |
256 | | #ifdef XP_WIN |
257 | | const char kPathSeparator = '\\'; |
258 | | #else |
259 | | const char kPathSeparator = '/'; |
260 | | #endif |
261 | | |
262 | | bool IsAllowedPath(const nsTSubstring<char_path_t>& aFilePath) |
263 | 177 | { |
264 | 177 | typedef TNormalizer<char_path_t> Normalizer; |
265 | 177 | // If sBlacklist has been cleared at shutdown, we must avoid calling |
266 | 177 | // PathBlacklist() again, as that will recreate the array and we will leak. |
267 | 177 | if (!sBlacklist) { |
268 | 177 | return true; |
269 | 177 | } |
270 | 0 | |
271 | 0 | if (PathBlacklist().Length() == 0) { |
272 | 0 | return true; |
273 | 0 | } |
274 | 0 | |
275 | 0 | nsTAutoString<char_path_t> normalized; |
276 | 0 | if (!Normalizer(aFilePath, Normalizer::Token::Char(kPathSeparator)).Get(normalized)) { |
277 | 0 | // Broken paths are considered invalid and thus inaccessible |
278 | 0 | return false; |
279 | 0 | } |
280 | 0 | |
281 | 0 | for (const auto& prefix : PathBlacklist()) { |
282 | 0 | if (StringBeginsWith(normalized, prefix)) { |
283 | 0 | if (normalized.Length() > prefix.Length() && |
284 | 0 | normalized[prefix.Length()] != kPathSeparator) { |
285 | 0 | continue; |
286 | 0 | } |
287 | 0 | return false; |
288 | 0 | } |
289 | 0 | } |
290 | 0 |
|
291 | 0 | return true; |
292 | 0 | } |
293 | | |
294 | | void testing::SetBlockUNCPaths(bool aBlock) |
295 | 0 | { |
296 | 0 | sBlockUNCPaths = aBlock; |
297 | 0 | } |
298 | | |
299 | | void testing::AddDirectoryToWhitelist(nsAString const & aPath) |
300 | 0 | { |
301 | 0 | PathWhitelist().AppendElement(aPath); |
302 | 0 | } |
303 | | |
304 | | bool testing::NormalizePath(nsAString const & aPath, nsAString & aNormalized) |
305 | 0 | { |
306 | 0 | typedef TNormalizer<char16_t> Normalizer; |
307 | 0 | Normalizer normalizer(aPath, Normalizer::Token::Char('\\')); |
308 | 0 | return normalizer.Get(aNormalized); |
309 | 0 | } |
310 | | |
311 | | } // ::FilePreferences |
312 | | } // ::mozilla |