source: trunk/eraser/Eraser.DefaultPlugins/FileSystems/Windows.cs @ 2559

Revision 2559, 14.7 KB checked in by lowjoel, 3 years ago (diff)

We should only check for FileSystemInfos? in the directory being erased if the directory is not a symbolic link. Otherwise, the check is meaningless.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2012 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Text;
26
27using System.IO;
28using System.Threading;
29
30using Eraser.Util;
31using Eraser.Util.ExtensionMethods;
32using Eraser.Plugins;
33using Eraser.Plugins.ExtensionPoints;
34
35namespace Eraser.DefaultPlugins
36{
37    /// <summary>
38    /// Base class for all Windows filesystems.
39    /// </summary>
40    abstract class WindowsFileSystem : FileSystemBase
41    {
42        public override void ResetFileTimes(FileSystemInfo info)
43        {
44            //Reset the file access times: after every rename the file times may change.
45            info.SetTimes(MinTimestamp, MinTimestamp, MinTimestamp, MinTimestamp);
46        }
47
48        public override void DeleteFile(FileInfo info)
49        {
50            //If the user wants plausible deniability, find a random file from the
51            //list of decoys and write it over.
52            if (Host.Instance.Settings.PlausibleDeniability)
53            {
54                using (FileStream fileStream = info.OpenWrite())
55                    CopyPlausibleDeniabilityFile(fileStream);
56            }
57
58            DeleteFileSystemInfo(info);
59        }
60
61        public override void DeleteFolder(DirectoryInfo info, bool recursive)
62        {
63            if ((info.Attributes & FileAttributes.ReparsePoint) == 0)
64            {
65                if (!recursive && info.GetFileSystemInfos().Length != 0)
66                    throw new InvalidOperationException(S._("The folder {0} cannot be deleted as it is " +
67                        "not empty."));
68
69                foreach (DirectoryInfo dir in info.GetDirectories())
70                    DeleteFolder(dir, true);
71                foreach (FileInfo file in info.GetFiles())
72                    DeleteFile(file);
73            }
74
75            DeleteFileSystemInfo(info);
76        }
77
78        /// <summary>
79        /// Deletes a directory or file from disk. This assumes that directories already
80        /// have been deleted.
81        /// </summary>
82        /// <param name="info">The file or directory to delete.</param>
83        private void DeleteFileSystemInfo(FileSystemInfo info)
84        {
85            //If the file/directory doesn't exist, pass.
86            if (!info.Exists)
87                return;
88
89            //Reset the file attributes to non-content indexed so indexing
90            //services will not lock the file.
91            try
92            {
93                info.Attributes = FileAttributes.NotContentIndexed;
94            }
95            catch (ArgumentException e)
96            {
97                //This is an undocumented exception: when the path we are setting
98                //cannot be accessed (ERROR_ACCESS_DENIED is returned) an
99                //ArgumentException is raised (no idea why!)
100                throw new UnauthorizedAccessException(e.Message, e);
101            }
102
103            //Rename the file a few times to erase the entry from the file system
104            //table.
105            for (int i = 0, tries = 0; i < FileNameErasePasses; ++tries)
106            {
107                //Generate a new file name for the file/directory.
108                string newPath = GenerateRandomFileName(info.GetParent(), info.Name.Length);
109
110                try
111                {
112                    ResetFileTimes(info);
113
114                    //Try to rename the file. If it fails, it is probably due to another
115                    //process locking the file. Defer, then rename again.
116                    info.MoveTo(newPath);
117                    ++i;
118                }
119                catch (IOException e)
120                {
121                    switch (System.Runtime.InteropServices.Marshal.GetLastWin32Error())
122                    {
123                        case Win32ErrorCode.AccessDenied:
124                            throw new UnauthorizedAccessException(S._("The file {0} could not " +
125                                "be erased because the file's permissions prevent access to the file.",
126                                info.FullName));
127
128                        case Win32ErrorCode.SharingViolation:
129                            //If after FilenameEraseTries the file is still locked, some program is
130                            //definitely using the file; throw an exception.
131                            if (tries > FileNameEraseTries)
132                            {
133                                //Try to force the handle closed.
134                                if (tries > FileNameEraseTries + 1 ||
135                                    !Host.Instance.Settings.ForceUnlockLockedFiles)
136                                {
137                                    throw new IOException(S._("The file {0} is currently in use " +
138                                        "and cannot be removed.", info.FullName), e);
139                                }
140
141                                StringBuilder processStr = new StringBuilder();
142                                foreach (OpenHandle handle in OpenHandle.Close(info.FullName))
143                                {
144                                    try
145                                    {
146                                        processStr.AppendFormat(
147                                            System.Globalization.CultureInfo.InvariantCulture,
148                                            "{0}, ", handle.Process.MainModule.FileName);
149                                    }
150                                    catch (System.ComponentModel.Win32Exception)
151                                    {
152                                        processStr.AppendFormat(
153                                            System.Globalization.CultureInfo.InvariantCulture,
154                                            "Process ID {0}, ", handle.Process.Id);
155                                    }
156                                }
157
158                                if (processStr.Length != 0)
159                                    Logger.Log(S._("Could not force closure of file \"{0}\" {1}",
160                                            info.FullName, S._("(locked by {0})",
161                                                processStr.ToString().Remove(processStr.Length - 2)).Trim()),
162                                        LogLevel.Error);
163                            }
164
165                            //Let the process locking the file release the lock
166                            Thread.Sleep(100);
167                            break;
168
169                        case Win32ErrorCode.DiskFull:
170                            //If the disk is full, we can't do anything except manually deleting
171                            //the file, break out of this loop.
172                            i = FileNameEraseTries;
173                            break;
174
175                        default:
176                            throw;
177                    }
178                }
179            }
180
181            //Then delete the file.
182            for (int i = 0; i < FileNameEraseTries; ++i)
183                try
184                {
185                    info.Delete();
186                    break;
187                }
188                catch (IOException e)
189                {
190                    switch (System.Runtime.InteropServices.Marshal.GetLastWin32Error())
191                    {
192                        case Win32ErrorCode.AccessDenied:
193                            throw new UnauthorizedAccessException(S._("The file {0} could not " +
194                                "be erased because the file's permissions prevent access to the file.",
195                                info.FullName), e);
196
197                        case Win32ErrorCode.SharingViolation:
198                            //If after FilenameEraseTries the file is still locked, some program is
199                            //definitely using the file; throw an exception.
200                            if (i > FileNameEraseTries)
201                                throw new IOException(S._("The file {0} is currently in use and " +
202                                    "cannot be removed.", info.FullName), e);
203
204                            //Let the process locking the file release the lock
205                            Thread.Sleep(100);
206                            break;
207
208                        default:
209                            throw;
210                    }
211                }
212        }
213
214
215        /// <summary>
216        /// Writes a file for plausible deniability over the current stream.
217        /// </summary>
218        /// <param name="stream">The stream to write the data to.</param>
219        private static void CopyPlausibleDeniabilityFile(Stream stream)
220        {
221            //Get the template file to copy
222            FileInfo shadowFileInfo;
223            {
224                string shadowFile = null;
225                List<string> entries = new List<string>(
226                    Host.Instance.Settings.PlausibleDeniabilityFiles);
227                IPrng prng = Host.Instance.Prngs.ActivePrng;
228                do
229                {
230                    if (entries.Count == 0)
231                        throw new FatalException(S._("Plausible deniability was selected, " +
232                            "but no decoy files were found. The current file has been only " +
233                            "replaced with random data."));
234
235                    //Get an item from the list of files, and then check that the item exists.
236                    int index = prng.Next(entries.Count - 1);
237                    shadowFile = entries[index];
238                    if (File.Exists(shadowFile) || Directory.Exists(shadowFile))
239                    {
240                        if ((File.GetAttributes(shadowFile) & FileAttributes.Directory) != 0)
241                        {
242                            DirectoryInfo dir = new DirectoryInfo(shadowFile);
243                            FileInfo[] files = dir.GetFiles("*", SearchOption.AllDirectories);
244                            entries.Capacity += files.Length;
245                            foreach (FileInfo f in files)
246                                entries.Add(f.FullName);
247                        }
248                        else
249                            shadowFile = entries[index];
250                    }
251                    else
252                        shadowFile = null;
253
254                    entries.RemoveAt(index);
255                }
256                while (string.IsNullOrEmpty(shadowFile));
257                shadowFileInfo = new FileInfo(shadowFile);
258            }
259
260            //Dump the copy (the first 4MB, or less, depending on the file size and size of
261            //the original file)
262            long amountToCopy = Math.Min(stream.Length,
263                Math.Min(4 * 1024 * 1024, shadowFileInfo.Length));
264            using (FileStream shadowFileStream = shadowFileInfo.OpenRead())
265            {
266                while (stream.Position < amountToCopy)
267                {
268                    byte[] buf = new byte[524288];
269                    int bytesRead = shadowFileStream.Read(buf, 0, buf.Length);
270
271                    //Stop bothering if the input stream is at the end
272                    if (bytesRead == 0)
273                        break;
274
275                    //Dump the read contents onto the file to be deleted
276                    stream.Write(buf, 0,
277                        (int)Math.Min(bytesRead, amountToCopy - stream.Position));
278                }
279            }
280        }
281
282        public override void EraseClusterTips(VolumeInfo info, IErasureMethod method,
283            ClusterTipsSearchProgress searchCallback, ClusterTipsEraseProgress eraseCallback)
284        {
285            //List all the files which can be erased.
286            List<string> files = new List<string>();
287            if (!info.IsMounted)
288                throw new InvalidOperationException(S._("Could not erase cluster tips in {0} " +
289                    "as the volume is not mounted.", info.VolumeId));
290            ListFiles(info.MountPoints[0], files, searchCallback);
291
292            //For every file, erase the cluster tips.
293            for (int i = 0, j = files.Count; i != j; ++i)
294            {
295                //Get the file attributes for restoring later
296                StreamInfo streamInfo = new StreamInfo(files[i]);
297                if (!streamInfo.Exists)
298                    continue;
299
300                try
301                {
302                    EraseFileClusterTips(streamInfo, method);
303                }
304                catch (UnauthorizedAccessException)
305                {
306                    Logger.Log(S._("{0} did not have its cluster tips erased because you do not " +
307                        "have the required permissions to erase the file cluster tips.", files[i]),
308                        LogLevel.Information);
309                }
310                catch (IOException e)
311                {
312                    Logger.Log(S._("{0} did not have its cluster tips erased. The error returned " +
313                        "was: {1}", files[i], e.Message), LogLevel.Warning);
314                }
315
316                eraseCallback(i, files.Count, files[i]);
317            }
318        }
319
320        private void ListFiles(DirectoryInfo info, List<string> files,
321            ClusterTipsSearchProgress searchCallback)
322        {
323            try
324            {
325                //Skip this directory if it is a reparse point
326                if ((info.Attributes & FileAttributes.ReparsePoint) != 0)
327                {
328                    Logger.Log(S._("Files in {0} did not have their cluster tips erased because " +
329                        "it is a hard link or a symbolic link.", info.FullName),
330                        LogLevel.Information);
331                    return;
332                }
333
334                foreach (FileInfo file in info.GetFiles())
335                    if (file.IsProtectedSystemFile())
336                        Logger.Log(S._("{0} did not have its cluster tips erased, because it is " +
337                            "a system file", file.FullName), LogLevel.Information);
338                    else if ((file.Attributes & FileAttributes.ReparsePoint) != 0)
339                        Logger.Log(S._("{0} did not have its cluster tips erased because it is a " +
340                            "hard link or a symbolic link.", file.FullName), LogLevel.Information);
341                    else if ((file.Attributes & FileAttributes.Compressed) != 0 ||
342                        (file.Attributes & FileAttributes.Encrypted) != 0 ||
343                        (file.Attributes & FileAttributes.SparseFile) != 0)
344                    {
345                        Logger.Log(S._("{0} did not have its cluster tips erased because it is " +
346                            "compressed, encrypted or a sparse file.", file.FullName),
347                            LogLevel.Information);
348                    }
349                    else
350                    {
351                        try
352                        {
353                            foreach (StreamInfo stream in file.GetADSes())
354                                files.Add(stream.FullName);
355
356                            files.Add(file.FullName);
357                        }
358                        catch (UnauthorizedAccessException e)
359                        {
360                            Logger.Log(S._("{0} did not have its cluster tips erased because of " +
361                                "the following error: {1}", file.FullName, e.Message),
362                                LogLevel.Information);
363                        }
364                        catch (IOException e)
365                        {
366                            Logger.Log(S._("{0} did not have its cluster tips erased because of " +
367                                "the following error: {1}", file.FullName, e.Message),
368                                LogLevel.Warning);
369                        }
370                    }
371
372                foreach (DirectoryInfo subDirInfo in info.GetDirectories())
373                {
374                    searchCallback(subDirInfo.FullName);
375                    ListFiles(subDirInfo, files, searchCallback);
376                }
377            }
378            catch (UnauthorizedAccessException e)
379            {
380                Logger.Log(S._("Files in {0} did not have its cluster tips erased because of the " +
381                    "following error: {1}", info.FullName, e.Message), LogLevel.Information);
382            }
383            catch (IOException e)
384            {
385                Logger.Log(S._("Files in {0} did not have its cluster tips erased because of the " +
386                    "following error: {1}", info.FullName, e.Message), LogLevel.Warning);
387            }
388        }
389
390        /// <summary>
391        /// Erases the cluster tips of the given file.
392        /// </summary>
393        /// <param name="stream">The stream to erase.</param>
394        /// <param name="method">The erasure method to use.</param>
395        private void EraseFileClusterTips(StreamInfo streamInfo, IErasureMethod method)
396        {
397            //Get the file access times
398            DateTime lastAccess = streamInfo.LastAccessTime;
399            DateTime lastWrite = streamInfo.LastWriteTime;
400            DateTime created = streamInfo.CreationTime;
401
402            //Get the file attributes
403            FileAttributes attributes = streamInfo.Attributes;
404
405            //And get the file lengths to know how much to overwrite
406            long fileArea = GetFileArea(streamInfo);
407            long fileLength = streamInfo.Length;
408
409            //If the file length equals the file area there is no cluster tip to overwrite
410            if (fileArea == fileLength)
411                return;
412
413            //Otherwise, unset any read-only flags, create the stream, lengthen the
414            //file, then tell the erasure method to erase the cluster tips.
415            try
416            {
417                streamInfo.Attributes = FileAttributes.Normal;
418                FileStream stream = streamInfo.Open(FileMode.Open, FileAccess.Write,
419                    FileShare.None, FileOptions.WriteThrough);
420
421                try
422                {
423                    stream.SetLength(fileArea);
424                    stream.Seek(fileLength, SeekOrigin.Begin);
425
426                    //Erase the file
427                    method.Erase(stream, long.MaxValue, Host.Instance.Prngs.ActivePrng, null);
428                }
429                finally
430                {
431                    //Make sure the file length is restored!
432                    stream.SetLength(fileLength);
433
434                    //Then destroy the stream
435                    stream.Close();
436                }
437            }
438            catch (ArgumentException e)
439            {
440                //This is an undocumented exception: when the path we are setting
441                //cannot be accessed (ERROR_ACCESS_DENIED is returned) an
442                //ArgumentException is raised (no idea why!)
443                throw new UnauthorizedAccessException(e.Message, e);
444            }
445            finally
446            {
447                //Reset the file attributes
448                if (streamInfo.Attributes != attributes)
449                    streamInfo.Attributes = attributes;
450
451                //Reset the file times
452                streamInfo.SetTimes(MinTimestamp, created, lastWrite, lastAccess);
453            }
454        }
455
456        public override long GetFileArea(StreamInfo info)
457        {
458            VolumeInfo volume = VolumeInfo.FromMountPoint(info.Directory.FullName);
459            long clusterSize = volume.ClusterSize;
460            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
461        }
462
463        /// <summary>
464        /// The minimum timestamp the file system can take. This is for secure file
465        /// deletion.
466        /// </summary>
467        protected abstract DateTime MinTimestamp { get; }
468    }
469}
Note: See TracBrowser for help on using the repository browser.