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

Revision 1015, 14.8 KB checked in by lowjoel, 6 years ago (diff)

-Reset a file's read-only flag only after an erase failed
-NTFS' minimum date is 1601 January 1 UTC. Make sure that it is UTC when converting dates (the earlier bug wasn't fixed.)

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