Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add domain and path checks for secure cookies eviction #14491

Merged
merged 3 commits into from Dec 24, 2016

Conversation

@KiChjang
Copy link
Member

KiChjang commented Dec 8, 2016

Fixes #14477.


This change is Reviewable

@highfive
Copy link

highfive commented Dec 8, 2016

Heads up! This PR modifies the following files:

  • @KiChjang: components/net/cookie_storage.rs, components/net/cookie.rs
@highfive
Copy link

highfive commented Dec 8, 2016

warning Warning warning

  • These commits modify net code, but no tests are modified. Please consider adding a test!
@KiChjang
Copy link
Member Author

KiChjang commented Dec 8, 2016

r? @jdm

@highfive highfive assigned jdm and unassigned nox Dec 8, 2016
// Step 1
// https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 2
if !cookie.cookie.secure && source == CookieSource::HTTP &&
cookies.iter().filter(|c| {

This comment has been minimized.

Copy link
@Ms2ger

Ms2ger Dec 8, 2016

Contributor

This looks like a more complicated way of writing .any(..)

This comment has been minimized.

Copy link
@KiChjang

KiChjang Dec 8, 2016

Author Member

Totally is, that function completely slipped my mind.

@KiChjang KiChjang force-pushed the KiChjang:cookie-checks branch from b987b9c to e9fa399 Dec 8, 2016
Copy link
Member

jdm left a comment

We will definitely need tests verifying that the attacks described in the spec fail.

@@ -62,6 +74,12 @@ impl CookieStorage {

// http://tools.ietf.org/html/rfc6265#section-5.3
pub fn push(&mut self, mut cookie: Cookie, source: CookieSource) {
// https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 1
if cookie.cookie.secure && source == CookieSource::HTTP {
// New cookie is not from a "secure" protocol, ignore.

This comment has been minimized.

Copy link
@jdm

jdm Dec 8, 2016

Member

CookieSource::HTTP only means that it's an HTTP/HTTPS request, rather than document.cookie. Let's add a ServoUrl argument to CookieSource::HTTP so we can check it here.

cookies.iter().any(|c| {
c.cookie.name == cookie.cookie.name &&
c.cookie.secure &&
domain_match(c.cookie.domain, cookie.cookie.domain) &&

This comment has been minimized.

Copy link
@jdm

jdm Dec 8, 2016

Member

This is missing the "vice-versa" part of step 2.1.3.

string == domain_string || (string.ends_with(domain_string) &&
string.as_bytes()[string.len()-domain_string.len()-1] == b'.' &&
string.parse::<Ipv4Addr>().is_err() &&
string.parse::<Ipv6Addr>().is_err())

This comment has been minimized.

Copy link
@jdm

jdm Dec 8, 2016

Member

Could we do everything after the || on a new line instead?

@@ -37,23 +37,35 @@ impl CookieStorage {
let domain = reg_host(cookie.cookie.domain.as_ref().unwrap_or(&"".to_string()));
let cookies = self.cookies_map.entry(domain).or_insert(vec![]);

// Step 1
// https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 2
if !cookie.cookie.secure && source == CookieSource::HTTP &&

This comment has been minimized.

Copy link
@jdm

jdm Dec 8, 2016

Member

We'll want to check the actual request URL here.


c.cookie.name == cookie.cookie.name &&
c.cookie.secure &&
c.cookie.domain == cookie.cookie.domain &&

This comment has been minimized.

Copy link
@jdm

jdm Dec 9, 2016

Member

What happened to performing the symmetric domain matches from the spec? That's different than checking if the domains for the two cookies are identical.

This comment has been minimized.

Copy link
@KiChjang

KiChjang Dec 9, 2016

Author Member

I was thinking that bi-directional domain-matching is equivalent to equality comparison. RFC 2965 states the following:

 Host A's name domain-matches host B's if

 *  their host name strings string-compare equal; or
 *  A is a HDN string and has the form NB, where N is a non-empty
    name string, B has the form .B', and B' is a HDN string.  (So,
    x.y.com domain-matches .Y.com but not Y.com.)

This comment has been minimized.

Copy link
@jdm

jdm Dec 9, 2016

Member

It's the second point that doesn't get covered by the equality check here, since if the domains are not strictly equal they can still match.

This comment has been minimized.

Copy link
@KiChjang

KiChjang Dec 10, 2016

Author Member

Yes, but since it's checking bi-directionally, I don't think it's possible where A domain-matches B and B domain-matches A but A is not equal to B.

This comment has been minimized.

Copy link
@KiChjang

KiChjang Dec 10, 2016

Author Member

Basically it boils down to the statement "if two strings are suffixes of each other, then they are equal".

This comment has been minimized.

Copy link
@KiChjang

KiChjang Dec 10, 2016

Author Member

Nevermind, the IETF draft did not say "and vice versa", it said "or vice versa".

Cookie::path_match(new, existing)
} else {
cookie.cookie.path == c.cookie.path
};

This comment has been minimized.

Copy link
@jdm

jdm Dec 9, 2016

Member

Our cookies always have a path according to https://github.com/KiChjang/servo/blob/cf08600e5f390b72a6e094e74633326bad204908/components/net/cookie.rs#L69-L74, so let's use expect on both of them instead.

false
string == domain_string ||
(string.ends_with(domain_string) &&
string.as_bytes()[string.len()-domain_string.len()-1] == b'.' &&

This comment has been minimized.

Copy link
@frewsxcv

frewsxcv Dec 9, 2016

Member

Should we add an assert here that string.len() > domain_string.len() ?

This comment has been minimized.

Copy link
@jdm

jdm Dec 9, 2016

Member

If string ends with domain_string, that is already implicitly true.

@KiChjang KiChjang force-pushed the KiChjang:cookie-checks branch from cf08600 to 5901f2d Dec 9, 2016
@KiChjang KiChjang removed the S-needs-tests label Dec 10, 2016
@KiChjang KiChjang force-pushed the KiChjang:cookie-checks branch from 67f6009 to 5cc9952 Dec 10, 2016
@jdm
Copy link
Member

jdm commented Dec 13, 2016

Note to self: be sure that these changes do not prevent a page using document.cookie to set a secure cookie if the page running the JS has a secure origin.

@bors-servo
Copy link
Contributor

bors-servo commented Dec 14, 2016

The latest upstream changes (presumably #14570) made this pull request unmergeable. Please resolve the merge conflicts.

Copy link
Member

jdm left a comment

Good start on the tests. We need more thorough coverage for some of the cases to be sure we're not breaking the existing implementation and to be sure we're properly blocking all the cases that the specification calls out.

@@ -37,6 +37,21 @@ impl CookieStorage {
let domain = reg_host(cookie.cookie.domain.as_ref().unwrap_or(&"".to_string()));
let cookies = self.cookies_map.entry(domain).or_insert(vec![]);

// https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 2
if !cookie.cookie.secure && !source.is_secure_protocol() &&
cookies.iter().any(|c|

This comment has been minimized.

Copy link
@jdm

jdm Dec 15, 2016

Member

Let's do:

if !cookie.cookie.secure && !source.is_secure_protocol() {
    let new_domain = cookie.cookie.domain.as_ref().unwrap();
    let new_path = cookie.cookie.path.as_ref().unwrap();

    let any_overlapping = cookies.iter().any(|c| {
        let existing_domain = c.cookie.domain.as_ref().unwrap();
        let existing_path = c.cookie.path.as_ref().unwrap();

        c.cookie.name == cookie.cookie.name &&
        c.cookie.secure &&
        (Cookie::domain_match(new_domain, existing_domain) ||
         Cookie::domain_match(existing_domain, new_domain)) &&
        Cookie::path_match(new_path, existing_path)
    });

    if any_overlapping {
        return Err(());
    }
}
pub enum CookieSource {
/// An HTTP API
HTTP,
HTTP(ServoUrl),
/// A non-HTTP API
NonHTTP,

This comment has been minimized.

Copy link
@jdm

jdm Dec 15, 2016

Member

I think we're going to have to add a ServoUrl to this case to, or we'll break allowing pages to set secure cookies via document.cookie when loaded via a secure scheme.

This comment has been minimized.

Copy link
@KiChjang

KiChjang Dec 18, 2016

Author Member

What would a non-secure non-HTTP source look like?

This comment has been minimized.

Copy link
@jdm

jdm Dec 19, 2016

Member

Using document.cookie on a site with an HTTP URL.

#[test]
fn test_insecure_cookies_cannot_evict_secure_cookie() {
let mut storage = CookieStorage::new(5);
let url = ServoUrl::parse("https://home.example.org:8888/cookie-parser?0001").unwrap();

This comment has been minimized.

Copy link
@jdm

jdm Dec 15, 2016

Member

Let's call this secure_url and reuse it later.

storage.push(cookie, source.clone());
}

let url = ServoUrl::parse("http://home.example.org:8888/cookie-parser?0001").unwrap();

This comment has been minimized.

Copy link
@jdm

jdm Dec 15, 2016

Member

Let's call this insecure_url.

add_cookie_to_storage(&mut storage, &url, "foo=value; Domain=home.example.org");
add_cookie_to_storage(&mut storage, &url, "foo2=value; Domain=.example.org");
add_cookie_to_storage(&mut storage, &url, "foo3=value; Path=/foo/bar");
add_cookie_to_storage(&mut storage, &url, "foo4=value; Path=/foo");

This comment has been minimized.

Copy link
@jdm

jdm Dec 15, 2016

Member

We need another test that tries setting these cookies using a secure scheme, too.

let source = CookieSource::HTTP(url.clone());
assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo4=bar; foo3=bar; foo4=value; foo=bar; foo2=bar");
}

This comment has been minimized.

Copy link
@jdm

jdm Dec 15, 2016

Member

We should add a test that it's still possible to set secure cookies using a NonHTTP source and a secure URL.

@KiChjang KiChjang force-pushed the KiChjang:cookie-checks branch from 5cc9952 to b31d8f6 Dec 18, 2016
@jdm
Copy link
Member

jdm commented Dec 19, 2016

Seems like the rebase went a bit wonky here?

@KiChjang KiChjang force-pushed the KiChjang:cookie-checks branch from b31d8f6 to 8509127 Dec 20, 2016
@KiChjang
Copy link
Member Author

KiChjang commented Dec 20, 2016

Still only rebased; haven't gotten around to address comments yet.

@KiChjang KiChjang force-pushed the KiChjang:cookie-checks branch from 8509127 to 294adc8 Dec 21, 2016
@jdm jdm added the S-fails-tidy label Dec 21, 2016
@KiChjang KiChjang force-pushed the KiChjang:cookie-checks branch from 294adc8 to 16f1947 Dec 22, 2016
@jdm
Copy link
Member

jdm commented Dec 23, 2016

I finally sat down and reasoned my way through the test results. This looks good to me; thanks for working on it!
@bors-servo: r+

@bors-servo
Copy link
Contributor

bors-servo commented Dec 23, 2016

📌 Commit 16f1947 has been approved by jdm

@bors-servo
Copy link
Contributor

bors-servo commented Dec 23, 2016

Testing commit 16f1947 with merge b13afcc...

bors-servo added a commit that referenced this pull request Dec 23, 2016
Add domain and path checks for secure cookies eviction

Fixes #14477.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/14491)
<!-- Reviewable:end -->
@bors-servo bors-servo merged commit 16f1947 into servo:master Dec 24, 2016
3 checks passed
3 checks passed
continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
homu Test successful
Details
@KiChjang KiChjang deleted the KiChjang:cookie-checks branch Dec 24, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

7 participants
You can’t perform that action at this time.