Thread (24 messages) 24 messages, 6 authors, 2022-11-14

Re: [PATCH] http: redact curl h2h3 headers in info

From: Glen Choo <hidden>
Date: 2022-11-10 22:14:23

Emily Shaffer [off-list ref] writes:
On Wed, Nov 09, 2022 at 12:52:31AM +0000, Glen Choo via GitGitGadget wrote:
quoted
With GIT_TRACE_CURL=1 or GIT_CURL_VERBOSE=1, sensitive headers like
"Authorization" and "Cookie" get redacted. However, since [1], curl's
h2h3 module also prints headers in its "info", which don't get redacted.
For example,

  echo 'github.com	TRUE	/	FALSE	1698960413304	o	foo=bar' >cookiefile &&
  GIT_TRACE_CURL=1 GIT_TRACE_CURL_NO_DATA=1 git \
    -c 'http.cookiefile=cookiefile' \
    -c 'http.version=' \
    ls-remote https://github.com/git/git refs/heads/main 2>output &&
  grep 'cookie' output

produces output like:

  23:04:16.920495 http.c:678              == Info: h2h3 [cookie: o=foo=bar]
  23:04:16.920562 http.c:637              => Send header: cookie: o=<redacted>

Teach http.c to check for h2h3 headers in info and redact them using the
existing header redaction logic.

[1] https://github.com/curl/curl/commit/f8c3724aa90472c0e617ddbbc420aa199971eb77

Signed-off-by: Glen Choo <redacted>
---
    http: redact curl h2h3 headers in info
    
    I initially sent this to the security list, but the general impression
    is that this isn't sensitive enough for an embargoed fix, so this is
    better discussed in the open instead.
    
    Since this comes from curl's HTTP2.0/3.0 module, this can be mitigated
    by setting http.version to 1.X, e.g. "git -c http.version=HTTP/1.1".
    
    According to [1], the susceptible curl versions appear to be 7.86.0,
    7.85.0, 7.84.0, 7.83.1, 7.83.0, 7.82.0, but I'm not sure which platforms
    are vulnerable.
    
    This patch fixes the issue on my machine running curl 7.85.0, so I think
    it is okay to merge as-is. That said, I would strongly prefer to add
    tests, but I haven't figured out how. In particular:
    
     * Do we have a way of using HTTP/2.0 in our tests? A cursory glance at
       our httpd config suggests that we only use HTTP/1.1.
     * How could we set up end-to-end tests to ensure that we're testing
       this against affected versions of curl? To avoid regressions, I'd
       also prefer to test against future versions of curl too.
    
    [1]
    https://github.com/curl/curl/commit/f8c3724aa90472c0e617ddbbc420aa199971eb77

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1377%2Fchooglen%2Fhttp%2Fredact-h2h3-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1377/chooglen/http/redact-h2h3-v1
Pull-Request: https://github.com/git/git/pull/1377

 http.c | 40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)
diff --git a/http.c b/http.c
index 5d0502f51fd..cbcc7c3f5b6 100644
--- a/http.c
+++ b/http.c
@@ -560,8 +560,10 @@ static void set_curl_keepalive(CURL *c)
 }
 #endif
 
-static void redact_sensitive_header(struct strbuf *header)
+/* Return 0 if redactions been made, 1 otherwise. */
Does it make sense to reverse the retval here?

`if (!redact_sensitive_header())` sounds like "if not redacted, ..." -
but here it means the opposite, right?
I struggled with this for a bit since I wasn't sure what the convention
is here. Enumerating some off the top of my head, we have:

- For 'booleans', we "0" for false and "1" for true (e.g.
  starts_with()).
- For functions that may fail with error, we have "0" for success and
  nonzero to signal the failure type (e.g. strbuf_getdelim()).
- For functions that don't fail we have "0" for "nothing was done" and
  "1" for something was done (e.g. skip_prefix()).

(Tangent: from a readability perspective, this is pretty poor. I need to
know beforehand whether or not the function may fail with error before I
know what the return value means?)

This probably falls into the last category, so for consistency, I think
this should return "1" for "redactions have happened" (as you
suggested).
quoted
+static int redact_sensitive_header(struct strbuf *header)
 {
+	int ret = 1;
 	const char *sensitive_header;
 
 	if (trace_curl_redact &&
@@ -575,6 +577,7 @@ static void redact_sensitive_header(struct strbuf *header)
 		/* Everything else is opaque and possibly sensitive */
 		strbuf_setlen(header,  sensitive_header - header->buf);
 		strbuf_addstr(header, " <redacted>");
+		ret = 0;
 	} else if (trace_curl_redact &&
 		   skip_iprefix(header->buf, "Cookie:", &sensitive_header)) {
 		struct strbuf redacted_header = STRBUF_INIT;
@@ -612,6 +615,27 @@ static void redact_sensitive_header(struct strbuf *header)
 
 		strbuf_setlen(header, sensitive_header - header->buf);
 		strbuf_addbuf(header, &redacted_header);
+		ret = 0;
+	}
+	return ret;
+}
+
+/* Redact headers in info */
+static void redact_sensitive_info_header(struct strbuf *header)
+{
+	const char *sensitive_header;
+
+	if (trace_curl_redact &&
+	    skip_iprefix(header->buf, "h2h3 [", &sensitive_header)) {
+		struct strbuf inner = STRBUF_INIT;
+
+		/* Drop the trailing "]" */
+		strbuf_add(&inner, sensitive_header, strlen(sensitive_header) - 1);
+		if (!redact_sensitive_header(&inner)) {
+			strbuf_setlen(header, strlen("h2h3 ["));
+			strbuf_addbuf(header, &inner);
+			strbuf_addch(header, ']');
I'd really like some more comments in this function - even just one
describing the string we're trying to redact, or showing a sample line.
Navigating string parsing is always a bit difficult.
Ah yes, I should include a description of the string.
quoted
+		}
 	}
 }
 
@@ -668,6 +692,18 @@ static void curl_dump_data(const char *text, unsigned char *ptr, size_t size)
 	strbuf_release(&out);
 }
 
+static void curl_print_info(char *data, size_t size)
Nit: Every other helper in this file calls it _dump_, so should this
also say _dump_ instead of _print_?
Sure, I have no opinion here, so I'll do that.
quoted
+{
+	struct strbuf buf = STRBUF_INIT;
+
+	strbuf_add(&buf, data, size);
+
+	redact_sensitive_info_header(&buf);
+	trace_printf_key(&trace_curl, "== Info: %s", buf.buf);
+
+	strbuf_release(&buf);
+}
+
 static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *userp)
 {
 	const char *text;
@@ -675,7 +711,7 @@ static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size,
 
 	switch (type) {
 	case CURLINFO_TEXT:
-		trace_printf_key(&trace_curl, "== Info: %s", data);
+		curl_print_info(data, size);
 		break;
 	case CURLINFO_HEADER_OUT:
 		text = "=> Send header";
base-commit: c03801e19cb8ab36e9c0d17ff3d5e0c3b0f24193
Otherwise functionally it seems fine to me. case CURLINFO_TEXT is the
one case that's not already using a curl_dump_* helper, so we're adding
one, and to that helper we're adding a call out to
redact_sensitive_header().

Thanks.
 - Emily
quoted
-- 
gitgitgadget
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help