Bug 47593 - Require a user gesture to open the file dialog
Summary: Require a user gesture to open the file dialog
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: DOM (show other bugs)
Version: 528+ (Nightly build)
Hardware: PC OS X 10.5
: P2 Normal
Assignee: Nobody
URL:
Keywords:
Depends on:
Blocks: 55102 55110 55104
  Show dependency treegraph
 
Reported: 2010-10-13 07:22 PDT by Johnny(Jianning) Ding
Modified: 2011-12-30 04:21 PST (History)
7 users (show)

See Also:


Attachments
patch v1 (5.00 KB, patch)
2010-10-14 05:07 PDT, Johnny(Jianning) Ding
abarth: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Johnny(Jianning) Ding 2010-10-13 07:22:07 PDT
<Input type=file> is represented as a file upload control in WebKit, it is a sensitive control since it can access users' local file(s). 
So ideally browser should only allow doing the file choosing behavior via user gesture, but so far only Firefox does.(Please refer to http://mxr.mozilla.org/mozilla/source/content/html/content/src/nsHTMLInputElement.cpp#1413)
A chromium bug(http://crbug.com/58319) is related to this bug.

For example, when running the following code, there is no file select dialog only in Firefox, other main-stream browsers show the file dialog.
<input type="file" id="f">
<script>setTimeout("document.getElementById('f').click();", 1);</script>

I suggest requiring a user gesture to open the file dialog in WebKit like Firefox does

To do this, there are two ways.
1) Only direct user gesture event can open the file dialog. The direct user gesture event(key/mouse) means the event must dispatch on the <input type=file>, which can ensure the file dialog is opened by users' true intention.
2) The file dialog can be opened as long as a user gesture happens at that time, which means you can open the file dialog in user gesture event dispatched on other elements, which may be more convenient

For example. when running the following code in WebKit, 
<input type="file" id="f">
<a onclick="document.getElementById('f').click();">click me</a>

In way 1, clicking the text "click me" will not open the file dialog, but the file dialog does open in way 2.

I personally prefer the way 1, since it's more safe.

Any suggestions?
Comment 1 Adam Barth 2010-10-13 09:36:34 PDT
I think either would be fine.  I'd go with the general user gesture approach first, but I don't feel strongly about it.
Comment 2 Alexey Proskuryakov 2010-10-13 12:10:49 PDT
Would that add any safety in the presence of FileReader? A site can read file content from a drop handler.
Comment 3 Adam Barth 2010-10-13 12:43:12 PDT
The Chromium bug is marked security sensitive because it has exploit details.  The issue is that the web site can spam the user with file picker dialogs.
Comment 4 Alexey Proskuryakov 2010-10-13 12:48:45 PDT
> The Chromium bug is marked security sensitive because it has exploit details.

I can't see the Chromium bug.

> The issue is that the web site can spam the user with file picker dialogs.

Is this different from spamming with alerts, confirms or modal dialogs?
Comment 5 Adam Barth 2010-10-13 12:54:16 PDT
> > The issue is that the web site can spam the user with file picker dialogs.
> 
> Is this different from spamming with alerts, confirms or modal dialogs?

Yes.  For two reasons: (1) we have mitigations in place for all of those and (2) the file picker is non-modal.

Requiring a user gesture here matches Firefox as well.  We plan to put in other mitigations at the browser level, but this one seems worth doing at the WebCore level.
Comment 6 Johnny(Jianning) Ding 2010-10-14 05:07:46 PDT
Created attachment 70727 [details]
patch v1

Thanks for comments! 
Patch V1 is ready.
Comment 7 Abhishek Arya 2010-10-15 17:26:58 PDT
Adam, can you please review.
Comment 8 Adam Barth 2010-10-15 17:38:53 PDT
Comment on attachment 70727 [details]
patch v1

View in context: https://bugs.webkit.org/attachment.cgi?id=70727&action=review

> WebCore/rendering/RenderFileUploadControl.cpp:128
> +    if (!(frame() && frame()->loader()->isProcessingUserGesture()))

We usually say !frame() || !frame() ...
Comment 9 Abhishek Arya 2010-10-15 17:51:49 PDT
Thanks a lot Adam for the quick response.

Johnny, i can fix the style nit and commit it for you ? Also, did anyone nominate you for committer access yet ?
Comment 10 Johnny(Jianning) Ding 2010-10-16 07:46:09 PDT
(In reply to comment #9)
> Thanks a lot Adam for the quick response.
> 
> Johnny, i can fix the style nit and commit it for you ? Also, did anyone nominate you for committer access yet ?

Abhishek, please help me fix the nit and land it. Thanks very much for the helps from you and Adam!

So far no-body nominated me for committer yet.
I have 10+ changes on WebKit, you can refer to them from 
http://trac.webkit.org/search?q=jnd%40chromium&noquickjump=1&changeset=on
http://trac.webkit.org/search?q=johnnyding.webkit%40gmail.com&noquickjump=1&changeset=on
Comment 11 Abhishek Arya 2010-10-16 09:48:30 PDT
Committed r69914: <http://trac.webkit.org/changeset/69914>
Comment 12 Andy Estes 2011-02-22 15:26:52 PST
(In reply to comment #5)
> > > The issue is that the web site can spam the user with file picker dialogs.
> > 
> > Is this different from spamming with alerts, confirms or modal dialogs?
> 
> Requiring a user gesture here matches Firefox as well.  We plan to put in other mitigations at the browser level, but this one seems worth doing at the WebCore level.

Interestingly, this will no longer be true in Firefox 4. They now allow .click() to be called programmatically on input elements (see <https://bugzilla.mozilla.org/show_bug.cgi?id=61098>) without an underlying user gesture. IE also allows this.

I think we should revert this change. It makes us incompatible with other major engines and with at least one major JavaScript framework that I'm aware of (SproutCore), which I think outweighs the benefits of mitigating file chooser spamming.
Comment 13 Andy Estes 2011-02-22 15:30:36 PST
I will note that from reading the mozilla bug it seems like Firefox has mitigation against creating modal dialogs in a loop, which they apply to file chooser dialogs as well. I'm not sure if we do something similar. This is the type of exploit they wish to avoid (copied from the bug):

myFileControl = doc.getElementById('file');
do {
  myFileControl.click();
} while (!fileContainsDirectionsToSecretVolcanoLair(myFileControl.files[0]));
Comment 14 Johnny(Jianning) Ding 2011-02-22 22:33:55 PST
(In reply to comment #13)
> I will note that from reading the mozilla bug it seems like Firefox has mitigation against creating modal dialogs in a loop, which they apply to file chooser dialogs as well. I'm not sure if we do something similar. This is the type of exploit they wish to avoid (copied from the bug):
> 
> myFileControl = doc.getElementById('file');
> do {
>   myFileControl.click();
> } while (!fileContainsDirectionsToSecretVolcanoLair(myFileControl.files[0]));

Thanks Andy.

After reading mozilla bug https://bugzilla.mozilla.org/show_bug.cgi?id=61098, I think it is to deal with how to avoid creating endless model dialog like alert, like you said it applies to file chooser dialogs as well in Firefox, but this webkit change is to disallow javascript to open the file dialog without user gesture. See the following test.


<input type=button value="click" onclick="open_file_dialog()" />
<input type=file id="file">
<script>
function open_file_dialog() {
  while (1) {
    document.getElementById("file").click();
  }
}
// try to open a file dialog in load stage.
document.getElementById("file").click();
</script>

In above test, function open_file_dialog opens file dialog in infinite loop.
In Firefox4, when loading the test, there is no file dialog, which is blocked because of no user gesture (we follow it from this webkit bug). When calling the function in user gesture stage, the patch for mozilla bug 61098 can allow only a few file dialogs (model dialog) to be popped up.

I test the following test case in Firefox4 and Safari with enabling popup blocker, they have same behavior. I think we should keep this patch.
Please correct me if I am wrong.
Comment 15 Andy Estes 2011-02-22 23:28:57 PST
Hi Johnny,

(In reply to comment #14)
> (In reply to comment #13) 
> After reading mozilla bug https://bugzilla.mozilla.org/show_bug.cgi?id=61098, I think it is to deal with how to avoid creating endless model dialog like alert, like you said it applies to file chooser dialogs as well in Firefox, but this webkit change is to disallow javascript to open the file dialog without user gesture.

Sorry, I linked to the wrong bug. The bug I meant to link to was <https://bugzilla.mozilla.org/show_bug.cgi?id=36619>, which tracks adding a click() method to file input elements. <https://bugzilla.mozilla.org/show_bug.cgi?id=61098> is also relevant here, but wasn't what I was basing my analysis on.

Firefox 4 integrates their implementation with their pop-up blocker, which (if enabled) allows programatic clicks to open a file picker after user confirmation. We don't, so such attempts are blocked regardless of the state of our pop-up blocker.

Firefox 4 also allows file pickers even if the programatic click() and the user gesture are separated by a timeout. Take the following test case for example:

<script>
    window.addEventListener('load', function() {
        document.getElementById('the-link').addEventListener('click', function() {
             setTimeout(function() {
                 document.getElementById('the-file-input').click();
             },1000);
        }, false);
    }, false);
</script>
<input id="the-file-input" type="file">
<a id='the-link' href="#">Programatically click the input.</a>

Firefox 4 will display a file picker one second after clicking 'Programatically click the input'; we won't, again regardless of pop-up blocker state.

There are valid reasons for content authors to programmatically send click events to file input elements via setTimeout() and I think we shouldn't break those use cases.
Comment 16 Andy Estes 2011-02-23 00:00:06 PST
Perhaps timers could remember the user gesture state of the event loop iteration that initiated them, then set the user gesture state accordingly when the timer fires. This would allow the click-via-setTimeout use case without allowing non-gesture-initiated clicks in general. Depending on how careful we want to be we could add protections like only forwarding gestures for timers less than a certain maximum delay and/or only propagating one level deep.
Comment 17 Johnny(Jianning) Ding 2011-02-23 03:00:09 PST
(In reply to comment #16)
> Perhaps timers could remember the user gesture state of the event loop iteration that initiated them, then set the user gesture state accordingly when the timer fires. This would allow the click-via-setTimeout use case without allowing non-gesture-initiated clicks in general. Depending on how careful we want to be we could add protections like only forwarding gestures for timers less than a certain maximum delay and/or only propagating one level deep.

I see, looks like a feature for gesture management. Make sense to me for supporting it, but we should avoid contents authors to abuse this feature, I will suggest that only a timer can remember the user gesture state and the new timeouts created inside that timeout can not inherit this gesture.

Adam, what do you think about Andy's suggestion?
Comment 18 Adam Barth 2011-02-23 16:00:22 PST
> Adam, what do you think about Andy's suggestion?

Sounds reasonable.  We might want to check what other browsers do.  The long term solution here is probably to write a spec for all these cases and socialize it with other browser vendors.  Otherwise we're going to be continually chasing our tail w.r.t. what web sites expect us to do.
Comment 19 Andy Estes 2011-02-23 16:32:15 PST
I'll do a little more research into how Firefox handles the setTimeout() case. I'll also file new bugs for these enhancements.
Comment 20 Andy Estes 2011-02-23 16:52:28 PST
(In reply to comment #19)
> I'll do a little more research into how Firefox handles the setTimeout() case. I'll also file new bugs for these enhancements.

I filed <https://bugs.webkit.org/show_bug.cgi?id=55102> and <https://bugs.webkit.org/show_bug.cgi?id=55104>.
Comment 21 Andy Estes 2011-02-23 18:12:12 PST
Johnny, while looking into this I noticed that the test for this patch passes even if I comment out the early return in RenderFileUploadControl::click(). This isn't so great :(

I was thinking about how to make this test more obvious, and I think the right thing to do is to have DumpRenderTree implement the UI delegates for the open panel (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles: and webView:runOpenPanelForFileButtonWithResultListener on the Mac) and have them log a message. Layout test expectations could then be based around the presence/absence of this log message.

That technique could be useful for this test as well as for the tests that will be necessary for the two dependent bugs.
Comment 22 Andy Estes 2011-02-23 18:17:07 PST
(In reply to comment #21)
> Johnny, while looking into this I noticed that the test for this patch passes even if I comment out the early return in RenderFileUploadControl::click(). This isn't so great :(
> 
> I was thinking about how to make this test more obvious, and I think the right thing to do is to have DumpRenderTree implement the UI delegates for the open panel (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles: and webView:runOpenPanelForFileButtonWithResultListener on the Mac) and have them log a message. Layout test expectations could then be based around the presence/absence of this log message.
> 
> That technique could be useful for this test as well as for the tests that will be necessary for the two dependent bugs.

I filed <https://bugs.webkit.org/show_bug.cgi?id=55110> about this.
Comment 23 Jon Leighton 2011-12-30 04:21:50 PST
FWIW, this change causes problems with programmatically adding files in QtWebKit. See https://bugs.webkit.org/show_bug.cgi?id=75385.