[whatwg] Two session history issues caused by navigating while a load is pending

It's time for another episode of everyone's favorite show: "Find the
session history spec bug".  Today's episode is a double-header of
related issues.

== Issue #1 ==

Suppose an attack page evil.html controls a separate frame F (e.g.
evil.html frames F, evil.html opened F as a popup window, or vice
versa).

We discovered that if evil.html causes F to

  1. load a.html
  2. start loading b.html
  3. load a.html#h

then step (3) cannot cancel the load of b.html.  That is, the final
session history from this sequence must be either

  a.html  <-- oldest
  a.html#h
  b.html  <-- current

or

  a.html <-- oldest
  b.html <-- current.

All browsers I tested gave one of the above two results.

Doing anything else breaks the web (we shipped this in Firefox Nightly
and people were unable to log into ingdirect.com, for example).  I
didn't investigate too thoroughly, but I believe what happens is, some
sites use a link with href "#" and then navigate themselves in the
link's onclick handler, without cancelling the click event.  In that
case, we do precisely steps 1-3 above.

As I read the spec, browsers are supposed to cancel the load of b.html
in step 3 above.  In the navigation algorithm [1], step 6 explicitly
cancels the load of b.html, because the load of b.html has not
matured.  So if I understand correctly, the spec is dictating behavior
that we know won't work and that no browser implements.

The presence of steps 6 and 8 in the algorithm suggest that the spec
is already trying to walk this line, so maybe I misunderstand what's
going on, either in my tests or in the spec.

== Issue #2 ==

Suppose again that evil.com controls a frame F, and evil.com causes F to

  1. load a.html
  2. load a.html#h
  3. start loading b.html
  4. go back

When we go back, we traverse the history [2] from a.html#h to a.html.
Per the spec, this doesn't cancel the load of b.html.

This caused a problem for us in Firefox because we create a session
history entry for b.html at the beginning of step 3 and insert it
after the current one.  Then, when the load of b.html completes, we
use whichever session history entry happens to be after the current
one, assuming that it was the session history entry we created
earlier.  In this case, our assumption is wrong, and we load b.html
into a.html#h's session history entry.  This is somewhat disastrous,
but I'd prefer not to elaborate further until we've shipped a fix for
this issue in all supported versions of Firefox.

The fix for this bug is not as simple as merely ensuring that the
session history entry's URL matches the document's URL.  Due to hash
navigations and pushstate, these URLs may not match even when we're
behaving correctly.

We fixed this bug by cancelling the load of b.html when you go back.
This matches Chrome's behavior in my tests [3].

Notice that this means we're cancelling an outstanding network load
due to a synchronous same-document load, which I said in part 1 breaks
the web.  But based on the (lack of) feedback we've received from our
test audience, it seems that cancelling the load of b.html does /not/
break the web if the navigation from a.html to a.html#h is a history
navigation.

The right thing to do is probably to load b.html after a.html, so the
final session history is

  a.html <-- oldest
  b.html <-- current.

I /think/ this is what the spec says should happen, but I'm not sure.
But matching the spec here would be difficult in our current
architecture, and anyway wouldn't match the one other browser I was
able to test, so perhaps a spec should be changed to match.

Regards,
-Justin

[1] http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#navigating-across-documents
[2] http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#history-traversal
[3] I wasn't able to test Opera or IE because my testcase was
cross-origin and other browsers forbid cross-origin back/forward.  The
testcase uses the fact that it's cross-origin to get the right timing,
so it's actually not clear to me how to test this behavior in other
browsers.

Received on Monday, 17 September 2012 02:15:41 UTC