-
Notifications
You must be signed in to change notification settings - Fork 228
Rule out sync bugs due to slice reuse (fixes #198) #199
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
base: master
Are you sure you want to change the base?
Conversation
| } | ||
| if c.allowCredentials { | ||
| headers["Access-Control-Allow-Credentials"] = headerTrue | ||
| headers.Set("Access-Control-Allow-Credentials", "true") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep the allocation optimization and avoid triggering the concurrency check, we could create a new slice with the same backing array. Something like headerTrue[:1]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then handlers could still mutate the first element of the shared backing array through the new slice...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They only append or set right? There should no slide[0] = val as far as I remember
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't understand your last comment... 🤔
What I meant in my earlier comment is that, even if you write
headers["Access-Control-Allow-Credentials"] = headerTrue[:1]the tests that I posted in #198 keep failing, partly because the first element of headerTrue can still be mutated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would it be mutated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The failing tests included in #198 show how. Here is a version of the mutating handler focused on ACAO:
func(w http.ResponseWriter, r *http.Request) {
acao := w.Header()["Access-Control-Allow-Credentials"] // aliases slices headerTrue, headerTrue[:1], etc.
if len(acao) > 0 {
acao[0] = "oops!"
fmt.Println(headerTrue[0]) // oops!
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an unlikely use case. The caller would have to assume there is a first element they did not add and would know what it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be right about the unlikelihood of such a use case, but the fact remains: a handler can corrupt a CORS middleware that wraps it and inadvertently introduce non-obvious synchronisation bugs (non-obvious because it would be natural to assume that the acao slice doesn't alias anything at package level of rs/cors). I think most users of your library would value the guarantee that no such bugs are possible more than saving a couple of allocations during middleware execution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rs For completeness, here are some benchmark results ("old" and "new" correspond to master and my branch, respectively):
goos: darwin
goarch: arm64
pkg: github.com/rs/cors
cpu: Apple M4
│ old │ new │
│ sec/op │ sec/op vs base │
Without-10 3.279n ± 2% 3.265n ± 6% ~ (p=0.732 n=6)
Default-10 98.96n ± 2% 184.55n ± 14% +86.48% (p=0.002 n=6)
AllowedOrigin-10 142.0n ± 1% 160.5n ± 10% +13.10% (p=0.002 n=6)
Preflight-10 247.7n ± 3% 339.9n ± 11% +37.20% (p=0.002 n=6)
PreflightHeader-10 269.6n ± 2% 358.3n ± 3% +32.93% (p=0.002 n=6)
PreflightAdversarialACRH-10 501.1n ± 18% 536.6n ± 2% ~ (p=0.065 n=6)
Wildcard/match-10 6.625n ± 1% 6.612n ± 1% ~ (p=0.240 n=6)
Wildcard/too_short-10 0.9658n ± 22% 0.9652n ± 3% ~ (p=0.413 n=6)
geomean 42.10n 50.20n +19.26%
│ old │ new │
│ B/op │ B/op vs base │
Without-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=6) ¹
Default-10 16.00 ± 0% 48.00 ± 0% +200.00% (p=0.002 n=6)
AllowedOrigin-10 16.00 ± 0% 32.00 ± 0% +100.00% (p=0.002 n=6)
Preflight-10 16.00 ± 0% 48.00 ± 0% +200.00% (p=0.002 n=6)
PreflightHeader-10 16.00 ± 0% 48.00 ± 0% +200.00% (p=0.002 n=6)
PreflightAdversarialACRH-10 40.00 ± 0% 56.00 ± 0% +40.00% (p=0.002 n=6)
Wildcard/match-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=6) ¹
Wildcard/too_short-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=6) ¹
geomean ² +71.72% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ old │ new │
│ allocs/op │ allocs/op vs base │
Without-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=6) ¹
Default-10 1.000 ± 0% 3.000 ± 0% +200.00% (p=0.002 n=6)
AllowedOrigin-10 1.000 ± 0% 2.000 ± 0% +100.00% (p=0.002 n=6)
Preflight-10 1.000 ± 0% 3.000 ± 0% +200.00% (p=0.002 n=6)
PreflightHeader-10 1.000 ± 0% 3.000 ± 0% +200.00% (p=0.002 n=6)
PreflightAdversarialACRH-10 2.000 ± 0% 3.000 ± 0% +50.00% (p=0.002 n=6)
Wildcard/match-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=6) ¹
Wildcard/too_short-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=6) ¹
geomean ² +73.21% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
No description provided.