Re: [whatwg/fetch] Handling Partial Content / 206 (#144)

I'm going to take another swing at this.

These cases should work:

* A media element makes a series of ranged requests. Each request arrives in the service worker, is fetched, and the response returned. This works without a service worker, so it should work in a pass-through case.
* A media element makes a series of ranged requests. The service worker uses a mixture of custom responses, and non-opaque responses. All of these responses are non-opaque, so there's no issue.
* A script element makes a request. The request arrives in the service worker, it's fetched, and the response is partial. The script element accepts and executes the partial response. It's super weird, but [browsers already allow this](https://partial-response-test.glitch.me/) without service worker. As @annevk said, we shouldn't break that as part of this effort. If we want to break this, we'll look at it separately.

Since the service worker can rewrite fetches, it opens up the following attacks:

## Attack 1

A media element makes two requests:

**Request:** Resource A. No-cors. Cross-origin. Byte range 0-5000.
**Response:** JS-constructed. Valid media container data, stopping before the bit that defines the duration of the media.

**Request:** Resource A. No-cors. Cross-origin. Byte range 200-5000.
**Response:** `fetch(event.request)`. Response is opaque.

In this case, resource A isn't a valid media resource, but its 200th byte is now leaked as `mediaElement.duration`.

**Solution:** The media element must not allow a mixture of opaque and non-opaque responses for a given piece of media.

## Attack 2

A media element makes two requests:

**Request:** Resource A. No-cors. Cross-origin. Byte range 0-5000.
**Response:** A fetch to resource B, which responds with opaque valid media container data, stopping before the bit that defines the duration of the media.

**Request:** Resource A. No-cors. Cross-origin. Byte range 200-5000.
**Response:** `fetch(event.request)`. Response is opaque.

Again, the 200th byte is leaked.

**Solution:** If the media element consumes opaque data, the first URL in each response's URL list must be identical.

## Attack 3

A media element makes two requests:

**Request:** Resource A. No-cors. Cross-origin. Byte range 0-5000.
**Response:** A previously cached response, part of resource A, byte range 8000-8200, which just happens to be opaque valid media container data, stopping before the bit that defines the duration of the media.

**Request:** Resource A. No-cors. Cross-origin. Byte range 200-5000.
**Response:** `fetch(event.request)`. Response is opaque.

Again, the 200th byte is leaked.

**Solution:** In the first fetch, the start of the byte range returned does not match the start of the byte range requested. This should be rejected.

We could reject this in the fetch spec, but I don't think we should block it for manual `fetch()` calls.

This should be blocked by the media element, but we could have a helper in the fetch spec to make this easier.

## Attack 4

A script element makes a request:

**Request:** Resource A. No-cors. Cross-origin.
**Response:** An opaque partial response of resource A that just happens to contain `gender = 'female'`, which is private user data.

In this case resource A is an html resource like:

```html
<p>Foo</p>
<script>const gender = 'female';</script>
<p>Bar</p>
```

…and tbe browser has been previously tricked (perhaps using a media element) into making a request for the range that contains `gender = 'female'`.

**Solution:** Given that the script element accepts partial responses, this is a tricky one. It seems that we want to continue to support the case where the server has, unprompted by the range header, returned a partial response. The difference in this case is the server *was* promted.

The response needs to know if its associated request had a Range header.

Fetch should reject if the original request *did not* have a range header, but the service worker provides a response that is opaque, partial, and *was* requested with a range header.

@annevk I'm interested in your thoughts on the solution for attack 4.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/fetch/issues/144#issuecomment-368040980

Received on Friday, 23 February 2018 15:29:30 UTC