Opened 17 years ago

Closed 16 years ago

Last modified 13 years ago

#4948 closed (fixed)

Saving FileField files is not thread-safe

Reported by: anonymous Owned by: Robert Coup
Component: Core (Other) Version: dev
Severity: Keywords: fs-rf-docs
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

Picking the filename in django.db.modes.Model._save_FIELD_file is not thread-safe. There is an obvious racing condition if one thread will have found a non-existing filename and then a second thread will search for a non-existing name based on the same filename before the first thread starts to write the file.

Because this function is probably most often called from the admin UI and the newforms frameworks doesn't support file upload yet, it isn't critical. Still, this kind of problems can cause difficult to debug problems.

Attachments (5)

safe_filename_gen__modelsonly.1.diff (1.7 KB ) - added by Robert Coup 17 years ago.
safe_filename_gen.1.diff (6.5 KB ) - added by Robert Coup 17 years ago.
Been doing this too long…
safe_filename_gen.1.2.diff (6.5 KB ) - added by Daniel Roseman 17 years ago.
Full patch+tests with better version of test 2
testfg0.jpg (1.7 KB ) - added by Daniel Roseman 17 years ago.
Test jpeg file showing corruption incurred during save.
t.py (531 bytes ) - added by Martin v. Löwis 16 years ago.
Demonstration of race condition

Download all attachments as: .zip

Change History (21)

comment:1 by Simon G. <dev@…>, 17 years ago

Triage Stage: UnreviewedAccepted

comment:2 by Robert Coup, 17 years ago

Owner: changed from nobody to Robert Coup
Status: newassigned

Attached patch (safe_filename_gen.1.diff) uses the standard open(path, O_CREAT | O_EXCL) pattern.

Since the tests in bug639 had perfectly good models and files, I renamed it to file_field and added tests into there. A patch to base.py is available in safe_filename_gen__modelsonly.1.diff.

by Robert Coup, 17 years ago

by Robert Coup, 17 years ago

Attachment: safe_filename_gen.1.diff added

Been doing this too long...

comment:3 by Robert Coup, 17 years ago

Malcolm pointed out this needs confirmation on Windows wrt. O_CREAT & O_EXCL

comment:4 by Daniel Roseman, 17 years ago

Patch needs improvement: set

Tested this on Windows using supplied regression tests - tests 2 and 3 both fail.

by Daniel Roseman, 17 years ago

Attachment: safe_filename_gen.1.2.diff added

Full patch+tests with better version of test 2

comment:5 by Daniel Roseman, 17 years ago

The failure in test 2 turned out to be a problem in the test itself - it wasn't using os.path.normpath to normalise a hard-coded file path. I've uploaded a new version of the full patch with this corrected.

However, test 3 - comparing the uploaded data with the original file - still fails. It seems to be corrupting the image during upload. I checked this with the non-patched SVN, and the test passes there, so there is something going on with this patch that's causing corruption on Windows.

comment:6 by Robert Coup, 17 years ago

If you skip tearDown() and manually inspect the files, do they the original jpeg (ie. is it just weird tests)?

in reply to:  6 comment:7 by Daniel Roseman, 17 years ago

I did try that - the saved file is definitely corrupt. With the default jpeg, you get the top two-thirds of the picture OK but then it starts looking very odd. I've tried it multiple times and the same corruption happens each time. I'll upload the corrupted jpg here.

I also tried it with other image files and the corruption is even worse.

by Daniel Roseman, 17 years ago

Attachment: testfg0.jpg added

Test jpeg file showing corruption incurred during save.

comment:8 by Robert Coup, 17 years ago

Maybe it's doing something in text mode, even thought its set to use binary. I'll do some more experiments when I get to work tomorrow.

comment:9 by Marty Alchin, 17 years ago

Keywords: fs-rf added

comment:10 by Marty Alchin, 17 years ago

Keywords: fs-rf-docs added; fs-rf removed

comment:11 by Marty Alchin, 16 years ago

milestone: 1.0 beta

comment:12 by Michael Axiak, 16 years ago

Since r7814 (the merge of #2070), there has been some file locking logic in save_FIELD_file.

To me at least, it seems like the fix in #2070 is just as adequate as the patches contained in this ticket (except cross-platform).

Of course, I don't believe the patch in this ticket or #2070 fixes this problem completely.

So the question: Anybody who was able to see this problem before, can you now? (post r7814)

Cheers,
Mike

comment:13 by Malcolm Tredinnick, 16 years ago

Okay, so I'm not convinced this is really fixed by the #2070 changes, but it is very hard to fix due to the increased separation of responsibilities that change introduced. Leaving for "later" for the time being, but we should look at this again.

by Martin v. Löwis, 16 years ago

Attachment: t.py added

Demonstration of race condition

comment:14 by Martin v. Löwis, 16 years ago

I believe the race condition still exists after the file storage refactoring;the attached t.py demonstrates it. If two threads simultaneously try to create a file, they will both create the same file, and the last writer wins. If the sleep(5) is activated between in the test, then the second writer will see that the file already exists, and create the file under a different name.

The current locking in the code doesn't really help - it only means that the content of the two different files will not interleave, but instead, that one fully overwrites the other.

What should happen instead is that the open flag for exclusive open is used, which causes failure to open if the file is already present. In that case, Storage.save should go back and ask for a different available name, as the first name has proven not to be available.

comment:15 by Jacob, 16 years ago

Resolution: fixed
Status: assignedclosed

(In [8306]) Fixed #4948, a race condition in file saving. Thanks to Martin von Löwis, who diagnosed the problem and pointed the way to a fix.

comment:16 by Jacob, 13 years ago

milestone: 1.0 beta

Milestone 1.0 beta deleted

Note: See TracTickets for help on using tickets.
Back to Top