source: branches/eraser6/Manager/FileSystem.cs @ 1032

Revision 1032, 14.9 KB checked in by lowjoel, 6 years ago (diff)

Support FAT32 as well (as part of the FatFileSystem? provider) - so declare it so

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By: Kasra Nassiri <cjax@users.sourceforge.net> @17/10/2008
6 * Modified By:
7 *
8 * This file is part of Eraser.
9 *
10 * Eraser is free software: you can redistribute it and/or modify it under the
11 * terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 3 of the License, or (at your option) any later
13 * version.
14 *
15 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
16 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 *
19 * A copy of the GNU General Public License can be found at
20 * <http://www.gnu.org/licenses/>.
21 */
22
23using System;
24using System.Collections.Generic;
25using System.Text;
26using Eraser.Util;
27using System.IO;
28using System.Threading;
29
30namespace Eraser.Manager
31{
32    /// <summary>
33    /// Provides functions to handle erasures specfic to file systems.
34    /// </summary>
35    public abstract class FileSystem
36    {
37        /// <summary>
38        /// Gets the FileSystem object that implements the FileSystem interface
39        /// for the given file system.
40        /// </summary>
41        /// <param name="volume">The volume to get the FileSystem provider for.</param>
42        /// <returns>The FileSystem object providing interfaces to handle the
43        /// given volume.</returns>
44        /// <exception cref="NotSupportedException">Thrown when an unimplemented
45        /// file system is requested.</exception>
46        public static FileSystem Get(VolumeInfo volume)
47        {
48            switch (volume.VolumeFormat)
49            {
50                case "FAT":
51                case "FAT32":
52                    return new FatFileSystem();
53                case "NTFS":
54                    return new NtfsFileSystem();
55            }
56
57            throw new NotSupportedException(S._("The file system on the drive {0} is not " +
58                "supported.", volume.IsMounted ? volume.MountPoints[0] : volume.VolumeId));
59        }
60
61        /// <summary>
62        /// Generates a random file name with the given length.
63        /// </summary>
64        /// <remarks>The generated file name is guaranteed not to exist.</remarks>
65        /// <param name="info">The directory to generate the file name in. This
66        /// parameter can be null to indicate merely a random file name</param>
67        /// <param name="length">The length of the file name to generate.</param>
68        /// <returns>A full path to a file containing random file name.</returns>
69        public static string GenerateRandomFileName(DirectoryInfo info, int length)
70        {
71            //Get a random file name
72            Prng prng = PrngManager.GetInstance(ManagerLibrary.Settings.ActivePrng);
73            string resultPrefix = info == null ? string.Empty : info.FullName +
74                Path.DirectorySeparatorChar;
75            byte[] resultAry = new byte[length];
76            string result = string.Empty;
77
78            do
79            {
80                prng.NextBytes(resultAry);
81
82                //Validate the name
83                string validFileNameChars = "0123456789abcdefghijklmnopqrstuvwxyz" +
84                    "ABCDEFGHIJKLMNOPQRSTUVWXYZ _+=-()[]{}',`~!";
85                for (int j = 0, k = resultAry.Length; j < k; ++j)
86                    resultAry[j] = (byte)validFileNameChars[
87                        (int)resultAry[j] % validFileNameChars.Length];
88
89                result = Encoding.UTF8.GetString(resultAry);
90            }
91            while (info != null && (Directory.Exists(resultPrefix + result) ||
92                System.IO.File.Exists(resultPrefix + result)));
93            return resultPrefix + result;
94        }
95
96        /// <summary>
97        /// Gets a random file from within the provided directory.
98        /// </summary>
99        /// <param name="info">The directory to get a random file name from.</param>
100        /// <returns>A string containing the full path to the file.</returns>
101        public static string GetRandomFile(DirectoryInfo info)
102        {
103            //First retrieve the list of files and folders in the provided directory.
104            FileSystemInfo[] entries = null;
105            try
106            {
107                entries = info.GetFileSystemInfos();
108            }
109            catch (Exception)
110            {
111                return string.Empty;
112            }
113            if (entries.Length == 0)
114                return string.Empty;
115
116            //Find a random entry.
117            Prng prng = PrngManager.GetInstance(ManagerLibrary.Settings.ActivePrng);
118            string result = string.Empty;
119            while (result.Length == 0)
120            {
121                int index = prng.Next(entries.Length - 1);
122                if (entries[index] is DirectoryInfo)
123                    result = GetRandomFile((DirectoryInfo)entries[index]);
124                else
125                    result = ((FileInfo)entries[index]).FullName;
126            }
127
128            return result;
129        }
130
131        /// <summary>
132        /// Writes a file for plausible deniability over the current stream.
133        /// </summary>
134        /// <param name="fileStream">The stream to write the data to.</param>
135        protected void CopyPlausibleDeniabilityFile(Stream destFileStream)
136        {
137            //Get the template file to copy
138            FileInfo shadowFileInfo;
139            {
140                string shadowFile = null;
141                List<string> entries = ManagerLibrary.Settings.PlausibleDeniabilityFiles.GetRange(
142                    0, ManagerLibrary.Settings.PlausibleDeniabilityFiles.Count);
143                Prng prng = PrngManager.GetInstance(ManagerLibrary.Settings.ActivePrng);
144                do
145                {
146                    if (entries.Count == 0)
147                        throw new FatalException(S._("Plausible deniability was selected, " +
148                            "but no decoy files were found. The current file has been only " +
149                            "replaced with random data."));
150
151                    int index = prng.Next(entries.Count - 1);
152                    if ((System.IO.File.GetAttributes(entries[index]) & FileAttributes.Directory) != 0)
153                    {
154                        DirectoryInfo dir = new DirectoryInfo(entries[index]);
155                        FileInfo[] files = dir.GetFiles("*", SearchOption.AllDirectories);
156                        foreach (FileInfo f in files)
157                            entries.Add(f.FullName);
158                    }
159                    else
160                        shadowFile = entries[index];
161
162                    entries.RemoveAt(index);
163                }
164                while (shadowFile == null || shadowFile.Length == 0);
165                shadowFileInfo = new FileInfo(shadowFile);
166            }
167
168            //Dump the copy (the first 4MB, or less, depending on the file size and available
169            //user space)
170            long amountToCopy = Math.Min(4 * 1024 * 1024, shadowFileInfo.Length);
171            using (FileStream shadowFileStream = shadowFileInfo.OpenRead())
172            {
173                while (destFileStream.Position < amountToCopy)
174                {
175                    byte[] buf = new byte[524288];
176                    int bytesRead = shadowFileStream.Read(buf, 0, buf.Length);
177
178                    //Stop bothering if the input stream is at the end
179                    if (bytesRead == 0)
180                        break;
181
182                    //Dump the read contents onto the file to be deleted
183                    destFileStream.Write(buf, 0,
184                        (int)Math.Min(bytesRead, amountToCopy - destFileStream.Position));
185                }
186            }
187        }
188
189        /// <summary>
190        /// Securely deletes the file reference from the directory structures
191        /// as well as resetting the Date Created, Date Accessed and Date Modified
192        /// records.
193        /// </summary>
194        /// <param name="info">The file to delete.</param>
195        public abstract void DeleteFile(FileInfo info);
196
197        /// <summary>
198        /// Securely deletes the folder reference from the directory structures
199        /// as well as all subfolders and files, resetting the Date Created, Date
200        /// Accessed and Date Modified records.
201        /// </summary>
202        /// <param name="info">The folder to delete</param>
203        public abstract void DeleteFolder(DirectoryInfo info);
204
205        /// <summary>
206        /// Erases old file system table-resident files. This creates small one-byte
207        /// files until disk is full. This will erase unused space which was used for
208        /// files resident in the file system table.
209        /// </summary>
210        /// <param name="info">The directory information structure containing
211        /// the path to store the temporary one-byte files. The file system table
212        /// of that drive will be erased.</param>
213        /// <param name="method">The method used to erase the files.</param>
214        public abstract void EraseOldFileSystemResidentFiles(VolumeInfo info,
215            ErasureMethod method, FileSystemEntriesEraseProgress callback);
216
217        /// <summary>
218        /// Erases the unused space in the main filesystem structures by creating,
219        /// files until the table grows.
220        ///
221        /// This will overwrite unused portions of the table which were previously
222        /// used to store file entries.
223        /// </summary>
224        /// <param name="info">The directory information structure containing
225        /// the path to store the temporary files.</param>
226        /// <param name="callback">The callback function to handle the progress
227        /// of the file system entry erasure.</param>
228        public abstract void EraseDirectoryStructures(VolumeInfo info,
229            FileSystemEntriesEraseProgress callback);
230
231        /// <summary>
232        /// The number of times file names are renamed to erase the file name from
233        /// the file system table.
234        /// </summary>
235        public const int FileNameErasePasses = 7;
236
237        /// <summary>
238        /// The maximum number of times Eraser tries to erase a file/folder before
239        /// it gives up.
240        /// </summary>
241        public const int FileNameEraseTries = 50;
242    }
243
244    /// <summary>
245    /// The prototype of callbacks handling the file system table erase progress.
246    /// </summary>
247    /// <param name="currentFile">The current file being erased.</param>
248    /// <param name="totalFiles">The estimated number of files that must be
249    /// erased.</param>
250    public delegate void FileSystemEntriesEraseProgress(int currentFile, int totalFiles);
251
252    /// <summary>
253    /// Base class for all Windows filesystems.
254    /// </summary>
255    public abstract class WindowsFileSystem : FileSystem
256    {
257        public override void DeleteFile(FileInfo info)
258        {
259            //Set the date of the file to be invalid to prevent forensic
260            //detection
261            info.CreationTime = info.LastWriteTime = info.LastAccessTime = MinTimestamp;
262            info.Attributes = FileAttributes.Normal;
263            info.Attributes = FileAttributes.NotContentIndexed;
264
265            //Rename the file a few times to erase the entry from the file system
266            //table.
267            string newPath = GenerateRandomFileName(info.Directory, info.Name.Length);
268            for (int i = 0, tries = 0; i < FileNameErasePasses; ++tries)
269            {
270                //Try to rename the file. If it fails, it is probably due to another
271                //process locking the file. Defer, then rename again.
272                try
273                {
274                    info.MoveTo(newPath);
275                    ++i;
276                }
277                catch (IOException)
278                {
279                    Thread.Sleep(100);
280
281                    //If after FilenameEraseTries the file is still locked, some program is
282                    //definitely using the file; throw an exception.
283                    if (tries > FileNameEraseTries)
284                        throw new IOException(S._("The file {0} is currently in use and " +
285                            "cannot be removed.", info.FullName));
286                }
287            }
288
289            //If the user wants plausible deniability, find a random file on the same
290            //volume and write it over.
291            if (Manager.ManagerLibrary.Settings.PlausibleDeniability)
292            {
293                CopyPlausibleDeniabilityFile(info.OpenWrite());
294            }
295
296            //Then delete the file.
297            for (int i = 0; i < FileNameEraseTries; ++i)
298                try
299                {
300                    info.Delete();
301                    break;
302                }
303                catch (IOException)
304                {
305                    if (i > FileNameEraseTries)
306                        throw new IOException(S._("The file {0} is currently in use and " +
307                            "cannot be removed.", info.FullName));
308                    Thread.Sleep(100);
309                }
310        }
311
312        public override void DeleteFolder(DirectoryInfo info)
313        {
314            //TODO: check for reparse points
315            foreach (DirectoryInfo dir in info.GetDirectories())
316                DeleteFolder(dir);
317            foreach (FileInfo file in info.GetFiles())
318                DeleteFile(file);
319
320            //Then clean up this folder.
321            for (int i = 0; i < FileNameErasePasses; ++i)
322            {
323                //Rename the folder.
324                string newPath = GenerateRandomFileName(info.Parent, info.Name.Length);
325
326                //Try to rename the file. If it fails, it is probably due to another
327                //process locking the file. Defer, then rename again.
328                try
329                {
330                    info.MoveTo(newPath);
331                }
332                catch (IOException)
333                {
334                    Thread.Sleep(100);
335                    --i;
336                }
337            }
338
339            //Remove the folder
340            info.Delete(true);
341        }
342
343        /// <summary>
344        /// The minimum timestamp the file system can take. This is for secure file
345        /// deletion.
346        /// </summary>
347        protected abstract DateTime MinTimestamp { get; }
348    }
349
350    /// <summary>
351    /// Provides functions to handle erasures specific to NTFS volumes.
352    /// </summary>
353    public class NtfsFileSystem : WindowsFileSystem
354    {
355        public override void EraseOldFileSystemResidentFiles(VolumeInfo info,
356            ErasureMethod method, FileSystemEntriesEraseProgress callback)
357        {
358            try
359            {
360                //Squeeze one-byte files until the volume or the MFT is full.
361                DirectoryInfo rootDir = new DirectoryInfo(info.MountPoints[0]);
362                long oldMFTSize = NtfsApi.GetMftValidSize(info);
363
364                for ( ; ; )
365                {
366                    //Open this stream
367                    using (FileStream strm = new FileStream(GenerateRandomFileName(rootDir, 18),
368                        FileMode.CreateNew, FileAccess.Write, FileShare.None, 8,
369                        FileOptions.WriteThrough))
370                    {
371                        //Stretch the file size to use up some of the resident space.
372                        strm.SetLength(1);
373
374                        //Then run the erase task
375                        method.Erase(strm, long.MaxValue,
376                            PrngManager.GetInstance(ManagerLibrary.Settings.ActivePrng),
377                            null);
378                    }
379
380                    //We can stop when the MFT has grown.
381                    if (NtfsApi.GetMftValidSize(info) > oldMFTSize)
382                        break;
383                }
384            }
385            catch (IOException)
386            {
387                //OK, enough squeezing.
388            }
389        }
390
391        public override void EraseDirectoryStructures(VolumeInfo info,
392            FileSystemEntriesEraseProgress callback)
393        {
394            //Create a directory to hold all the temporary files
395            DirectoryInfo tempDir = new DirectoryInfo(info.MountPoints[0]);
396            tempDir = new DirectoryInfo(FileSystem.GenerateRandomFileName(tempDir, 32));
397            tempDir.Create();
398
399            try
400            {
401                //Get the size of the MFT
402                long mftSize = NtfsApi.GetMftValidSize(info);
403                long mftRecordSegmentSize = NtfsApi.GetMftRecordSegmentSize(info);
404                int pollingInterval = (int)Math.Max(1, (mftSize / info.ClusterSize / 20));
405                int totalFiles = (int)Math.Max(1L, mftSize / mftRecordSegmentSize) *
406                    (FileNameErasePasses + 1);
407                int filesCreated = 0;
408
409                while (true)
410                {
411                    ++filesCreated;
412                    using (FileStream strm = new FileStream(FileSystem.GenerateRandomFileName(
413                        tempDir, 220), FileMode.CreateNew, FileAccess.Write))
414                    {
415                    }
416
417                    if (filesCreated % pollingInterval == 0)
418                    {
419                        if (callback != null)
420                            callback(filesCreated, totalFiles);
421
422                        //Check if the MFT has grown.
423                        if (mftSize < NtfsApi.GetMftValidSize(info))
424                            break;
425                    }
426                }
427            }
428            catch (IOException)
429            {
430            }
431            finally
432            {
433                //Clear up all the temporary files
434                FileInfo[] files = tempDir.GetFiles("*", SearchOption.AllDirectories);
435                int totalFiles = files.Length * (FileNameErasePasses + 1);
436                for (int i = 0; i < files.Length; ++i)
437                {
438                    if (callback != null && i % 50 == 0)
439                        callback(files.Length + i * FileNameErasePasses, totalFiles);
440                    DeleteFile(files[i]);
441                }
442
443                DeleteFolder(tempDir);
444            }
445        }
446
447        protected override DateTime MinTimestamp
448        {
449            get
450            {
451                return new DateTime(1601, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
452            }
453        }
454    }
455
456    /// <summary>
457    /// Provides functions to handle erasures specific to FAT volumes.
458    /// </summary>
459    public class FatFileSystem : WindowsFileSystem
460    {
461        public override void EraseOldFileSystemResidentFiles(VolumeInfo info,
462            ErasureMethod method, FileSystemEntriesEraseProgress callback)
463        {
464            //Nothing to be done here. FAT doesn't store files in its FAT.
465        }
466
467        public override void EraseDirectoryStructures(VolumeInfo info,
468            FileSystemEntriesEraseProgress callback)
469        {
470            throw new NotImplementedException();
471        }
472
473        protected override DateTime MinTimestamp
474        {
475            get
476            {
477                return new DateTime(1980, 1, 1, 0, 0, 0);
478            }
479        }
480    }
481}
Note: See TracBrowser for help on using the repository browser.