Author: toad
Date: 2007-03-17 20:31:38 +0000 (Sat, 17 Mar 2007)
New Revision: 12198

Added:
   trunk/freenet/src/freenet/support/io/FileExistsException.java
Modified:
   trunk/freenet/src/freenet/client/ArchiveManager.java
   trunk/freenet/src/freenet/clients/http/QueueToadlet.java
   trunk/freenet/src/freenet/node/TextModeClientInterface.java
   trunk/freenet/src/freenet/node/fcp/ClientGet.java
   trunk/freenet/src/freenet/node/fcp/ClientPut.java
   trunk/freenet/src/freenet/node/fcp/ClientPutDir.java
   trunk/freenet/src/freenet/node/fcp/ClientPutDiskDirMessage.java
   trunk/freenet/src/freenet/node/fcp/ClientPutMessage.java
   trunk/freenet/src/freenet/node/fcp/DiskDirPutFile.java
   trunk/freenet/src/freenet/support/io/FileBucket.java
   trunk/freenet/src/freenet/support/io/FileBucketFactory.java
   trunk/freenet/src/freenet/support/io/PersistentTempBucketFactory.java
   trunk/freenet/src/freenet/support/io/SerializableToFieldSetBucketUtil.java
   trunk/freenet/src/freenet/support/io/TempFileBucket.java
Log:
Prevent symlink attacks when using DDA.

Modified: trunk/freenet/src/freenet/client/ArchiveManager.java
===================================================================
--- trunk/freenet/src/freenet/client/ArchiveManager.java        2007-03-17 
19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/client/ArchiveManager.java        2007-03-17 
20:31:38 UTC (rev 12198)
@@ -406,7 +406,7 @@
         */
        private TempStoreElement makeTempStoreBucket(long size) {
                File myFile = filenameGenerator.makeRandomFilename();
-               FileBucket fb = new FileBucket(myFile, false, true, true, true);
+               FileBucket fb = new FileBucket(myFile, false, false, true, 
true, true);

                byte[] cipherKey = new byte[32];
                random.nextBytes(cipherKey);

Modified: trunk/freenet/src/freenet/clients/http/QueueToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/QueueToadlet.java    2007-03-17 
19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/clients/http/QueueToadlet.java    2007-03-17 
20:31:38 UTC (rev 12198)
@@ -225,7 +225,7 @@
                                String identifier = file.getName() + "-fred-" + 
System.currentTimeMillis();
                                String contentType = 
DefaultMIMETypes.guessMIMEType(filename, false);
                                try {
-                                       ClientPut clientPut = new 
ClientPut(fcp.getGlobalClient(), new FreenetURI("CHK@"), identifier, 
Integer.MAX_VALUE, RequestStarter.BULK_SPLITFILE_PRIORITY_CLASS, 
ClientRequest.PERSIST_FOREVER, null, false, false, -1, 
ClientPutMessage.UPLOAD_FROM_DISK, file, contentType, new FileBucket(file, 
true, false, false, false), null, file.getName(), false);
+                                       ClientPut clientPut = new 
ClientPut(fcp.getGlobalClient(), new FreenetURI("CHK@"), identifier, 
Integer.MAX_VALUE, RequestStarter.BULK_SPLITFILE_PRIORITY_CLASS, 
ClientRequest.PERSIST_FOREVER, null, false, false, -1, 
ClientPutMessage.UPLOAD_FROM_DISK, file, contentType, new FileBucket(file, 
true, false, false, false, false), null, file.getName(), false);
                                        if(logMINOR) Logger.minor(this, 
"Started global request to insert "+file+" to CHK@ as "+identifier);
                                        clientPut.start();
                                        fcp.forceStorePersistentRequests();

Modified: trunk/freenet/src/freenet/node/TextModeClientInterface.java
===================================================================
--- trunk/freenet/src/freenet/node/TextModeClientInterface.java 2007-03-17 
19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/node/TextModeClientInterface.java 2007-03-17 
20:31:38 UTC (rev 12198)
@@ -559,7 +559,7 @@
                if(mimeType.equals(DefaultMIMETypes.DEFAULT_MIME_TYPE))
                        mimeType = ""; // don't need to override it

-               FileBucket fb = new FileBucket(f, true, false, false, false);
+               FileBucket fb = new FileBucket(f, true, false, false, false, 
false);
                InsertBlock block = new InsertBlock(fb, new 
ClientMetadata(mimeType), FreenetURI.EMPTY_CHK_URI);

                startTime = System.currentTimeMillis();
@@ -899,7 +899,7 @@
                if (filelist[i].isFile()) {
                        File f = filelist[i];

-                       FileBucket bucket = new FileBucket(f, true, false, 
false, false);
+                       FileBucket bucket = new FileBucket(f, true, false, 
false, false, false);

                        ret.put(f.getName(), bucket);
                } else if(filelist[i].isDirectory()) {

Modified: trunk/freenet/src/freenet/node/fcp/ClientGet.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientGet.java   2007-03-17 19:14:14 UTC 
(rev 12197)
+++ trunk/freenet/src/freenet/node/fcp/ClientGet.java   2007-03-17 20:31:38 UTC 
(rev 12198)
@@ -90,7 +90,7 @@
                if(returnType == ClientGetMessage.RETURN_TYPE_DISK) {
                        this.targetFile = returnFilename;
                        this.tempFile = returnTempFilename;
-                       ret = new FileBucket(returnTempFilename, false, false, 
false, false);
+                       ret = new FileBucket(returnTempFilename, false, true, 
false, false, false);
                } else if(returnType == ClientGetMessage.RETURN_TYPE_NONE) {
                        targetFile = null;
                        tempFile = null;
@@ -147,7 +147,7 @@
                if(returnType == ClientGetMessage.RETURN_TYPE_DISK) {
                        this.targetFile = message.diskFile;
                        this.tempFile = message.tempFile;
-                       ret = new FileBucket(message.tempFile, false, false, 
false, false);
+                       ret = new FileBucket(message.tempFile, false, true, 
false, false, false);
                } else if(returnType == ClientGetMessage.RETURN_TYPE_NONE) {
                        targetFile = null;
                        tempFile = null;
@@ -229,9 +229,9 @@
                Bucket ret = null;
                if(returnType == ClientGetMessage.RETURN_TYPE_DISK) {
                        if (succeeded) {
-                               ret = new FileBucket(targetFile, false, false, 
false, false);
+                               ret = new FileBucket(targetFile, false, true, 
false, false, false);
                        } else {
-                               ret = new FileBucket(tempFile, false, false, 
false, false);
+                               ret = new FileBucket(tempFile, false, true, 
false, false, false);
                        }
                } else if(returnType == ClientGetMessage.RETURN_TYPE_NONE) {
                        ret = new NullBucket();
@@ -343,7 +343,7 @@
                                                postFetchProtocolErrorMessage = 
new ProtocolErrorMessage(ProtocolErrorMessage.COULD_NOT_RENAME_FILE, false, 
null, identifier, global);
                                                // Don't delete temp file, user 
might want it.
                                        }
-                                       returnBucket = new 
FileBucket(targetFile, false, false, false, false);
+                                       returnBucket = new 
FileBucket(targetFile, false, true, false, false, false);
                                } catch (FileNotFoundException e) {
                                        postFetchProtocolErrorMessage = new 
ProtocolErrorMessage(ProtocolErrorMessage.COULD_NOT_WRITE_FILE, false, null, 
identifier, global);
                                } catch (IOException e) {
@@ -643,9 +643,9 @@
                synchronized(this) {
                        if(targetFile != null) {
                                if(succeeded || tempFile == null)
-                                       return new FileBucket(targetFile, 
false, false, false, false);
+                                       return new FileBucket(targetFile, 
false, true, false, false, false);
                                else
-                                       return new FileBucket(tempFile, false, 
false, false, false);
+                                       return new FileBucket(tempFile, false, 
true, false, false, false);
                        } else return returnBucket;
                }
        }

Modified: trunk/freenet/src/freenet/node/fcp/ClientPut.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientPut.java   2007-03-17 19:14:14 UTC 
(rev 12197)
+++ trunk/freenet/src/freenet/node/fcp/ClientPut.java   2007-03-17 20:31:38 UTC 
(rev 12198)
@@ -229,7 +229,7 @@
                        origFilename = new File(fs.get("Filename"));
                        if(logMINOR)
                                Logger.minor(this, "Uploading from disk: 
"+origFilename+" for "+this);
-                       data = new FileBucket(origFilename, true, false, false, 
false);
+                       data = new FileBucket(origFilename, true, false, false, 
false, false);
                        targetURI = null;
                } else if(uploadFrom == ClientPutMessage.UPLOAD_FROM_DIRECT) {
                        origFilename = null;

Modified: trunk/freenet/src/freenet/node/fcp/ClientPutDir.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientPutDir.java        2007-03-17 
19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/node/fcp/ClientPutDir.java        2007-03-17 
20:31:38 UTC (rev 12198)
@@ -119,7 +119,7 @@
                                        Logger.error(this, "File no longer 
exists, cancelling upload: "+ff);
                                        throw new IOException("File no longer 
exists, cancelling upload: "+ff);
                                }
-                               data = new FileBucket(ff, true, false, false, 
false);
+                               data = new FileBucket(ff, true, false, false, 
false, false);
                                me = new ManifestElement(name, data, 
contentTypeOverride, sz);
                                fileCount++;
                        } else if(uploadFrom.equalsIgnoreCase("redirect")) {

Modified: trunk/freenet/src/freenet/node/fcp/ClientPutDiskDirMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientPutDiskDirMessage.java     
2007-03-17 19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/node/fcp/ClientPutDiskDirMessage.java     
2007-03-17 20:31:38 UTC (rev 12198)
@@ -77,7 +77,7 @@
                        if (filelist[i].isFile()) {
                                File f = filelist[i];

-                               FileBucket bucket = new FileBucket(f, true, 
false, false, false);
+                               FileBucket bucket = new FileBucket(f, true, 
false, false, false, false);

                                ret.put(f.getName(), new 
ManifestElement(f.getName(), prefix + f.getName(), bucket, 
DefaultMIMETypes.guessMIMEType(f.getName(), true), f.length()));
                        } else if(filelist[i].isDirectory()) {

Modified: trunk/freenet/src/freenet/node/fcp/ClientPutMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientPutMessage.java    2007-03-17 
19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/node/fcp/ClientPutMessage.java    2007-03-17 
20:31:38 UTC (rev 12198)
@@ -140,7 +140,7 @@
                        if(!(f.exists() && f.isFile() && f.canRead()))
                                throw new 
MessageInvalidException(ProtocolErrorMessage.FILE_NOT_FOUND, null, identifier, 
global);
                        dataLength = f.length();
-                       FileBucket fileBucket = new FileBucket(f, true, false, 
false, false);
+                       FileBucket fileBucket = new FileBucket(f, true, false, 
false, false, false);
                        this.bucket = fileBucket;
                        this.origFilename = f;
                        redirectTarget = null;

Modified: trunk/freenet/src/freenet/node/fcp/DiskDirPutFile.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/DiskDirPutFile.java      2007-03-17 
19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/node/fcp/DiskDirPutFile.java      2007-03-17 
20:31:38 UTC (rev 12198)
@@ -31,7 +31,7 @@
        }

        public Bucket getData() {
-               return new FileBucket(file, true, false, false, false);
+               return new FileBucket(file, true, false, false, false, false);
        }

 }

Modified: trunk/freenet/src/freenet/support/io/FileBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/io/FileBucket.java        2007-03-17 
19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/support/io/FileBucket.java        2007-03-17 
20:31:38 UTC (rev 12198)
@@ -27,6 +27,8 @@
        protected boolean readOnly;
        protected boolean deleteOnFinalize;
        protected boolean deleteOnFree;
+       protected final boolean deleteOnExit;
+       protected final boolean createFileOnly;
        protected long length;
        // JVM caches File.size() and there is no way to flush the cache, so we
        // need to track it ourselves
@@ -38,17 +40,26 @@
         * Creates a new FileBucket.
         * 
         * @param file The File to read and write to.
+        * @param createFileOnly If true, create the file if it doesn't exist, 
but if it does exist,
+        * throw a FileExistsException on any write operation. This is safe 
against symlink attacks
+        * because we write to a temp file and then rename. It is technically 
possible that the rename
+        * will clobber an existing file if there is a race condition, but 
since it will not write over
+        * a symlink this is probably not dangerous. User-supplied filenames 
should in any case be
+        * restricted by higher levels.
         * @param readOnly If true, any attempt to write to the bucket will 
result in an IOException.
         * Can be set later. Irreversible. @see isReadOnly(), setReadOnly()
         * @param deleteOnFinalize If true, delete the file on finalization. 
Reversible.
         * @param deleteOnExit If true, delete the file on a clean exit of the 
JVM. Irreversible - use with care!
         */
-       public FileBucket(File file, boolean readOnly, boolean 
deleteOnFinalize, boolean deleteOnExit, boolean deleteOnFree) {
+       public FileBucket(File file, boolean readOnly, boolean createFileOnly, 
boolean deleteOnFinalize, boolean deleteOnExit, boolean deleteOnFree) {
                if(file == null) throw new NullPointerException();
+               file = file.getAbsoluteFile();
                this.readOnly = readOnly;
+               this.createFileOnly = createFileOnly;
                this.file = file;
                this.deleteOnFinalize = deleteOnFinalize;
                this.deleteOnFree = deleteOnFree;
+               this.deleteOnExit = deleteOnExit;
                if(deleteOnExit) {
                        try {
                                file.deleteOnExit();
@@ -74,28 +85,28 @@
        /**
         * Creates a new FileBucket in a random temporary file in the temporary
         * directory.
+        * @throws IOException 
         */
-       public FileBucket(RandomSource random) {
+       public FileBucket(RandomSource random) throws IOException {
                // **FIXME**/TODO: locking on tempDir needs to be checked by a 
Java guru for consistency
-               file =
-                       new File(
-                               tempDir,
-                    't'
-                            + Integer.toHexString(
-                                               Math.abs(random.nextInt())));
+               file = File.createTempFile(tempDir, ".freenet.tmp");
+               createFileOnly = true;
                // Useful for finding temp file leaks.
                //System.err.println("-- FileBucket.ctr(1) -- " +
                // file.getAbsolutePath());
                //(new Exception("get stack")).printStackTrace();
                deleteOnFinalize = true;
+               deleteOnExit = true;
                length = 0;
                file.deleteOnExit();
        }

        public FileBucket(SimpleFieldSet fs, PersistentFileTracker f) throws 
CannotCreateFromFieldSetException {
+               createFileOnly = true;
+               deleteOnExit = false;
                String tmp = fs.get("Filename");
                if(tmp == null) throw new CannotCreateFromFieldSetException("No 
filename");
-               this.file = new File(tmp);
+               this.file = new File(tmp).getAbsoluteFile();
                tmp = fs.get("Length");
                if(tmp == null) throw new CannotCreateFromFieldSetException("No 
length");
                try {
@@ -112,19 +123,31 @@
                synchronized (this) {
                        if(readOnly)
                                throw new IOException("Bucket is read-only");
-
+                       
+                       if(createFileOnly && file.exists())
+                               throw new FileExistsException(file);
+                       
                        // FIXME: behaviour depends on UNIX semantics, to 
totally abstract
                        // it out we would have to kill the old write streams 
here
                        // FIXME: what about existing streams? Will ones on 
append append
                        // to the new truncated file? Do we want them to? What 
about
                        // truncated ones? We should kill old streams here, 
right?
-                       return newFileBucketOutputStream(file.getPath(), 
++fileRestartCounter);
+                       return newFileBucketOutputStream(createFileOnly ? 
getTempfile() : file, file.getPath(), ++fileRestartCounter);
                }
        }

+       /**
+        * Create a temporary file in the same directory as this file.
+        */
+       protected File getTempfile() throws IOException {
+               File f = File.createTempFile(file.getName(), ".freenet-tmp", 
file.getParentFile());
+               if(deleteOnExit) f.deleteOnExit();
+               return f;
+       }
+       
        protected FileBucketOutputStream newFileBucketOutputStream(
-               String s, long streamNumber) throws IOException {
-               return new FileBucketOutputStream(s, streamNumber);
+               File tempfile, String s, long streamNumber) throws IOException {
+               return new FileBucketOutputStream(tempfile, s, streamNumber);
        }

        protected synchronized void resetLength() {
@@ -134,11 +157,13 @@
        class FileBucketOutputStream extends FileOutputStream {

                private long restartCount;
+               private File tempfile;

                protected FileBucketOutputStream(
-                       String s, long restartCount)
+                       File tempfile, String s, long restartCount)
                        throws FileNotFoundException {
-                       super(s, false);
+                       super(tempfile, false);
+                       this.tempfile = tempfile;
                        resetLength();
                        this.restartCount = restartCount;
                }
@@ -173,6 +198,26 @@
                                length++;
                        }
                }
+               
+               public void close() throws IOException {
+                       try {
+                               super.close();
+                       } catch (IOException e) {
+                               if(createFileOnly) tempfile.delete();
+                               throw e;
+                       }
+                       if(createFileOnly) {
+                               if(file.exists()) {
+                                       tempfile.delete();
+                                       throw new FileExistsException(file);
+                               }
+                               if(!tempfile.renameTo(file)) {
+                                       if(file.exists()) throw new 
FileExistsException(file);
+                                       tempfile.delete();
+                                       throw new IOException("Cannot rename 
file");
+                               }
+                       }
+               }
        }

        class FileBucketInputStream extends FileInputStream {

Modified: trunk/freenet/src/freenet/support/io/FileBucketFactory.java
===================================================================
--- trunk/freenet/src/freenet/support/io/FileBucketFactory.java 2007-03-17 
19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/support/io/FileBucketFactory.java 2007-03-17 
20:31:38 UTC (rev 12198)
@@ -10,37 +10,18 @@

 public class FileBucketFactory implements BucketFactory {

-    private int enumm = 0;
     private Vector files = new Vector();

     // Must have trailing "/"
-    public String rootDir = "";
+    public final File rootDir;

-    public FileBucketFactory() {
-        
+    public FileBucketFactory(File rootDir) {
+        this.rootDir = rootDir;
     }

-    public FileBucketFactory(String rootDir) {
-        this.rootDir = (rootDir.endsWith(File.separator)
-                        ? rootDir
-                        : (rootDir + File.separator));
-    }
-
-    public FileBucketFactory(File dir) {
-        this(dir.toString());
-    }
-
-    public Bucket makeBucket(long size) {
-        File f;
-        do {
-            f = new File(rootDir + "bffile_" + ++enumm);
-            // REDFLAG: remove hoaky debugging code
-            // System.err.println("----------------------------------------");
-            // Exception e = new Exception("created: " + f.getName());
-            // e.printStackTrace();
-            // System.err.println("----------------------------------------");
-        } while (f.exists());
-        Bucket b = new FileBucket(f, false, true, false, true);
+    public Bucket makeBucket(long size) throws IOException {
+       File f = File.createTempFile("bf_", ".freenet-tmp", rootDir);
+        Bucket b = new FileBucket(f, false, true, true, false, true);
         files.addElement(f);
         return b;
     }

Added: trunk/freenet/src/freenet/support/io/FileExistsException.java
===================================================================
--- trunk/freenet/src/freenet/support/io/FileExistsException.java               
                (rev 0)
+++ trunk/freenet/src/freenet/support/io/FileExistsException.java       
2007-03-17 20:31:38 UTC (rev 12198)
@@ -0,0 +1,19 @@
+package freenet.support.io;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Thrown when a FileBucket is required to create a new file but not overwrite 
an existing file,
+ * and the file exists.
+ */
+public class FileExistsException extends IOException {
+       
+       public final File file;
+       
+       public FileExistsException(File f) {
+               super("File exists: "+f);
+               this.file = f;
+       }
+
+}

Modified: trunk/freenet/src/freenet/support/io/PersistentTempBucketFactory.java
===================================================================
--- trunk/freenet/src/freenet/support/io/PersistentTempBucketFactory.java       
2007-03-17 19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/support/io/PersistentTempBucketFactory.java       
2007-03-17 20:31:38 UTC (rev 12198)
@@ -71,22 +71,6 @@
                bucketsToFree = new LinkedList();
        }

-       /**
-        * Called by a client to fetch the bucket denoted by a specific 
filename,
-        * and to register this fact so that it is not deleted on startup 
completion.
-        * @throws IOException 
-        */
-       public Bucket register(String filename, boolean mustExist) throws 
IOException {
-               File f = new File(dir, filename);
-               if(mustExist && !f.exists())
-                       throw new IOException("File does not exist (deleted?): 
"+f);
-               Bucket b = new FileBucket(f, false, false, false, true);
-               synchronized(this) {
-                       originalFiles.remove(f);
-               }
-               return b;
-       }
-       
        public void register(File file) {
                synchronized(this) {
                        originalFiles.remove(file);
@@ -106,7 +90,7 @@
        }

        private Bucket makeRawBucket(long size) throws IOException {
-               return new FileBucket(fg.makeRandomFilename(), false, false, 
false, true);
+               return new FileBucket(fg.makeRandomFilename(), false, true, 
false, false, true);
        }

        public Bucket makeBucket(long size) throws IOException {

Modified: 
trunk/freenet/src/freenet/support/io/SerializableToFieldSetBucketUtil.java
===================================================================
--- trunk/freenet/src/freenet/support/io/SerializableToFieldSetBucketUtil.java  
2007-03-17 19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/support/io/SerializableToFieldSetBucketUtil.java  
2007-03-17 20:31:38 UTC (rev 12198)
@@ -44,7 +44,7 @@
                                        if(persistent.exists()) fnam = 
persistent;
                                }
                                f.register(fnam);
-                               FileBucket fb = new FileBucket(fnam, false, 
false, false, true);
+                               FileBucket fb = new FileBucket(fnam, false, 
true, false, false, true);
                                try {
                                        PaddedEphemerallyEncryptedBucket eb = 
                                                new 
PaddedEphemerallyEncryptedBucket(fb, 1024, len, decryptKey, random, true);

Modified: trunk/freenet/src/freenet/support/io/TempFileBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/io/TempFileBucket.java    2007-03-17 
19:14:14 UTC (rev 12197)
+++ trunk/freenet/src/freenet/support/io/TempFileBucket.java    2007-03-17 
20:31:38 UTC (rev 12198)
@@ -38,7 +38,7 @@
                long minAlloc,
                float factor)
                throws IOException {
-               super(f, false, true, true, true);
+               super(f, false, false, true, true, true);
                synchronized(this) {
                        logDebug = Logger.shouldLog(Logger.DEBUG, this);
                }
@@ -274,7 +274,7 @@
                if (hook != null)
                        return new HookedFileBucketOutputStream(s, 
restartCount);
                else
-                       return super.newFileBucketOutputStream(s, restartCount);
+                       return super.newFileBucketOutputStream(new File(s), s, 
restartCount);
        }

        protected synchronized void deleteFile() {
@@ -353,7 +353,7 @@
                        String s,
                        long restartCount)
                        throws IOException {
-                       super(s, restartCount);
+                       super(new File(s), s, restartCount);
                        streams.addElement(this);
                        if (Logger.shouldLog(Logger.DEBUG, this))
                                Logger.debug(


Reply via email to