Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsAnonymousTemporaryFile.h"
8 #include "nsDirectoryServiceUtils.h"
9 #include "nsDirectoryServiceDefs.h"
10 #include "nsXULAppAPI.h"
11 #include "nsCOMPtr.h"
12 #include "nsString.h"
13 #include "nsAppDirectoryServiceDefs.h"
15 #ifdef XP_WIN
16 #include "nsIObserver.h"
17 #include "nsIObserverService.h"
18 #include "mozilla/Services.h"
19 #include "nsIIdleService.h"
20 #include "nsISimpleEnumerator.h"
21 #include "nsIFile.h"
22 #include "nsAutoPtr.h"
23 #include "nsITimer.h"
24 #include "nsCRT.h"
26 using namespace mozilla;
27 #endif
30 // We store the temp files in the system temp dir.
31 //
32 // On Windows systems in particular we use a sub-directory of the temp
33 // directory, because:
34 // 1. DELETE_ON_CLOSE is unreliable on Windows, in particular if we power
35 // cycle (and perhaps if we crash) the files are not deleted. We store
36 // the temporary files in a known sub-dir so that we can find and delete
37 // them easily and quickly.
38 // 2. On Windows NT the system temp dir is in the user's $HomeDir/AppData,
39 // so we can be sure the user always has write privileges to that directory;
40 // if the sub-dir for our temp files was in some shared location and
41 // was created by a privileged user, it's possible that other users
42 // wouldn't have write access to that sub-dir. (Non-Windows systems
43 // don't store their temp files in a sub-dir, so this isn't an issue on
44 // those platforms).
45 // 3. Content processes can access the system temp dir
46 // (NS_GetSpecialDirectory fails on NS_APP_USER_PROFILE_LOCAL_50_DIR
47 // for content process for example, which is where we previously stored
48 // temp files on Windows). This argument applies to all platforms, not
49 // just Windows.
50 static nsresult
51 GetTempDir(nsIFile** aTempDir)
52 {
53 if (NS_WARN_IF(!aTempDir))
54 return NS_ERROR_INVALID_ARG;
55 nsCOMPtr<nsIFile> tmpFile;
56 nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile));
57 if (NS_WARN_IF(NS_FAILED(rv)))
58 return rv;
60 #ifdef XP_WIN
61 // On windows DELETE_ON_CLOSE is unreliable, so we store temporary files
62 // in a subdir of the temp dir and delete that in an idle service observer
63 // to ensure it's been cleared.
64 rv = tmpFile->AppendNative(nsDependentCString("mozilla-temp-files"));
65 if (NS_WARN_IF(NS_FAILED(rv)))
66 return rv;
67 rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
68 if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv)))
69 return rv;
70 #endif
72 tmpFile.forget(aTempDir);
74 return NS_OK;
75 }
77 nsresult
78 NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc)
79 {
80 if (NS_WARN_IF(!aOutFileDesc))
81 return NS_ERROR_INVALID_ARG;
82 nsresult rv;
84 nsCOMPtr<nsIFile> tmpFile;
85 rv = GetTempDir(getter_AddRefs(tmpFile));
86 if (NS_WARN_IF(NS_FAILED(rv)))
87 return rv;
89 // Give the temp file a name with a random element. CreateUnique will also
90 // append a counter to the name if it encounters a name collision. Adding
91 // a random element to the name reduces the likelihood of a name collision,
92 // so that CreateUnique() doesn't end up trying a lot of name variants in
93 // its "try appending an incrementing counter" loop, as file IO can be
94 // expensive on some mobile flash drives.
95 nsAutoCString name("mozilla-temp-");
96 name.AppendInt(rand());
98 rv = tmpFile->AppendNative(name);
99 if (NS_WARN_IF(NS_FAILED(rv)))
100 return rv;
102 rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700);
103 if (NS_WARN_IF(NS_FAILED(rv)))
104 return rv;
106 rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE,
107 PR_IRWXU, aOutFileDesc);
109 return rv;
110 }
112 #ifdef XP_WIN
114 // On Windows we have an idle service observer that runs some time after
115 // startup and deletes any stray anonymous temporary files...
117 // Duration of idle time before we'll get a callback whereupon we attempt to
118 // remove any stray and unused anonymous temp files.
119 #define TEMP_FILE_IDLE_TIME_S 30
121 // The nsAnonTempFileRemover is created in a timer, which sets an idle observer.
122 // This is expiration time (in ms) which initial timer is set for (3 minutes).
123 #define SCHEDULE_TIMEOUT_MS 3 * 60 * 1000
125 #define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown"
127 // This class adds itself as an idle observer. When the application has
128 // been idle for about 30 seconds we'll get a notification, whereupon we'll
129 // attempt to delete ${TempDir}/mozilla-temp-files/. This is to ensure all
130 // temp files that were supposed to be deleted on application exit were actually
131 // deleted, as they may not be if we previously crashed. See bugs 572579 and
132 // 785662. This is only needed on some versions of Windows,
133 // nsIFile::DELETE_ON_CLOSE works on other platforms.
134 // This class adds itself as a shutdown observer so that it can cancel the
135 // idle observer and its timer on shutdown. Note: the observer and idle
136 // services hold references to instances of this object, and those references
137 // are what keep this object alive.
138 class nsAnonTempFileRemover MOZ_FINAL : public nsIObserver {
139 public:
140 NS_DECL_ISUPPORTS
142 nsAnonTempFileRemover() {
143 MOZ_COUNT_CTOR(nsAnonTempFileRemover);
144 }
146 ~nsAnonTempFileRemover() {
147 MOZ_COUNT_DTOR(nsAnonTempFileRemover);
148 }
150 nsresult Init() {
151 // We add the idle observer in a timer, so that the app has enough
152 // time to start up before we add the idle observer. If we register the
153 // idle observer too early, it will be registered before the fake idle
154 // service is installed when running in xpcshell, and this interferes with
155 // the fake idle service, causing xpcshell-test failures.
156 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
157 if (NS_WARN_IF(!mTimer))
158 return NS_ERROR_FAILURE;
159 nsresult rv = mTimer->Init(this,
160 SCHEDULE_TIMEOUT_MS,
161 nsITimer::TYPE_ONE_SHOT);
162 if (NS_WARN_IF(NS_FAILED(rv)))
163 return rv;
165 // Register shutdown observer so we can cancel the timer if we shutdown before
166 // the timer runs.
167 nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService();
168 if (NS_WARN_IF(!obsSrv))
169 return NS_ERROR_FAILURE;
170 return obsSrv->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
171 }
173 void Cleanup() {
174 // Cancel timer.
175 if (mTimer) {
176 mTimer->Cancel();
177 mTimer = nullptr;
178 }
179 // Remove idle service observer.
180 nsCOMPtr<nsIIdleService> idleSvc =
181 do_GetService("@mozilla.org/widget/idleservice;1");
182 if (idleSvc) {
183 idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME_S);
184 }
185 // Remove shutdown observer.
186 nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService();
187 if (obsSrv) {
188 obsSrv->RemoveObserver(this, XPCOM_SHUTDOWN_TOPIC);
189 }
190 }
192 NS_IMETHODIMP Observe(nsISupports *aSubject,
193 const char *aTopic,
194 const char16_t *aData)
195 {
196 if (nsCRT::strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0 &&
197 NS_FAILED(RegisterIdleObserver())) {
198 Cleanup();
199 } else if (nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0) {
200 // The user has been idle for a while, clean up the temp files.
201 // The idle service will drop its reference to this object after
202 // we exit, destroying this object.
203 RemoveAnonTempFileFiles();
204 Cleanup();
205 } else if (nsCRT::strcmp(aTopic, XPCOM_SHUTDOWN_TOPIC) == 0) {
206 Cleanup();
207 }
208 return NS_OK;
209 }
211 nsresult RegisterIdleObserver() {
212 // Add this as an idle observer. When we've been idle for
213 // TEMP_FILE_IDLE_TIME_S seconds, we'll get a notification, and we'll then
214 // try to delete any stray temp files.
215 nsCOMPtr<nsIIdleService> idleSvc =
216 do_GetService("@mozilla.org/widget/idleservice;1");
217 if (!idleSvc)
218 return NS_ERROR_FAILURE;
219 return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME_S);
220 }
222 void RemoveAnonTempFileFiles() {
223 nsCOMPtr<nsIFile> tmpDir;
224 nsresult rv = GetTempDir(getter_AddRefs(tmpDir));
225 if (NS_WARN_IF(NS_FAILED(rv)))
226 return;
228 // Remove the directory recursively.
229 tmpDir->Remove(true);
230 }
232 private:
233 nsCOMPtr<nsITimer> mTimer;
234 };
236 NS_IMPL_ISUPPORTS(nsAnonTempFileRemover, nsIObserver)
238 nsresult CreateAnonTempFileRemover() {
239 // Create a temp file remover. If Init() succeeds, the temp file remover is kept
240 // alive by a reference held by the observer service, since the temp file remover
241 // is a shutdown observer. We only create the temp file remover if we're running
242 // in the main process; there's no point in doing the temp file removal multiple
243 // times per startup.
244 if (XRE_GetProcessType() != GeckoProcessType_Default) {
245 return NS_OK;
246 }
247 nsRefPtr<nsAnonTempFileRemover> tempRemover = new nsAnonTempFileRemover();
248 return tempRemover->Init();
249 }
251 #endif