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

fix: validate that a flag key is valid UTF-8 & implemented fuzzing tests #141

Merged
merged 2 commits into from
Jan 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Run unit tests with `make test`.

#### Integration tests

The continuous integration runs a set of [gherkin integration tests](https://github.com/open-feature/test-harness/blob/main/features) using [`flagd`](https://github.com/open-feature/flagd).
The continuous integration runs a set of [gherkin integration tests](https://github.com/open-feature/test-harness/blob/main/features) using the [flagd provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/flagd) and [flagd](https://github.com/open-feature/flagd).
If you'd like to run them locally, first pull the `test-harness` git submodule
```
git submodule update --init --recursive
Expand All @@ -119,6 +119,20 @@ docker run -p 8013:8013 -v $PWD/test-harness/testing-flags.json:/testing-flags.j
make integration-test
```

#### Fuzzing

[Go supports fuzzing natively as of 1.18](https://go.dev/security/fuzz/).
The fuzzing suite is implemented as an integration of `go-sdk` with the [flagd provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/flagd) and [flagd](https://github.com/open-feature/flagd).
The fuzzing tests are found in [./integration/evaluation_fuzz_test.go](./integration/evaluation_fuzz_test.go), they are dependent on the flagd testbed running, you can start it with
```
docker run -p 8013:8013 ghcr.io/open-feature/flagd-testbed:latest
```
then, to execute a fuzzing test, run the following
```
go test -fuzz=FuzzBooleanEvaluation ./integration/evaluation_fuzz_test.go
```
substituting the name of the fuzz as appropriate.

### Releases

This repo uses Release Please to release packages. Release Please sets up a running PR that tracks all changes for the library components, and maintains the versions according to conventional commits, generated when PRs are merged. When Release Please's running PR is merged, any changed artifacts are published.
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ require (
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rs/cors v1.8.3 // indirect
github.com/rs/xid v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
Expand Down
15 changes: 2 additions & 13 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/diegoholiveira/jsonlogic/v3 v3.2.6 h1:EV607wRY72hT3V90ZOQw+zjXR9KIUV9jnHfT3yS8uks=
github.com/diegoholiveira/jsonlogic/v3 v3.2.6/go.mod h1:9oE8z9G+0OMxOoLHF3fhek3KuqD5CBqM0B6XFL08MSg=
github.com/diegoholiveira/jsonlogic/v3 v3.2.7 h1:awX07pFPnlntZzRNBcO4a2Ivxa77NMt+narq/6xcS0E=
github.com/diegoholiveira/jsonlogic/v3 v3.2.7/go.mod h1:9oE8z9G+0OMxOoLHF3fhek3KuqD5CBqM0B6XFL08MSg=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
Expand Down Expand Up @@ -207,14 +205,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/open-feature/flagd v0.3.1 h1:8MhBcNPWB9k1/y+HTnNnMAlQd+vLApFSuCf6Jx++xdg=
github.com/open-feature/flagd v0.3.1/go.mod h1:r/8ojycJpCzXINl7Y5KrSx2qu+fsEUA2TxmxVQnHO7s=
github.com/open-feature/flagd v0.3.2 h1:Xfmtd8z2LQ80ux2nwymNx8GrLebsLfbtDF3bFC1nVVM=
github.com/open-feature/flagd v0.3.2/go.mod h1:Xlle3jks+TBtZ6MKl/AiAeSB5FocSEwdq5CjO1O5eUs=
github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.2 h1:RxuWl1D9MJJIzOpDA2cue0g9hmgMX8Nt4eV6jBt/+4E=
github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.2/go.mod h1:6wbRT4QeBJXkfyWnntXZbxm1noDTVEmDUmzrCW5kbEA=
github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.3 h1:jT9WOr7hfDHgwxsfZoS07Mm9FxXJdw2TiZ2z5TOJA1k=
github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.3/go.mod h1:A4ZQULeRsddk5g2p/yrdH6mjZKH4xNp9DW3pEG4Z8fs=
github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.4 h1:AkaY+pqcjHQe8W/ZFdfJpsPGsxoLyPRYHpTMIhiboAw=
github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.4/go.mod h1:5U1Ry0iFy4j466JafVdK210E7wo6YODKnoaREyhCiHo=
github.com/open-feature/schemas v0.2.8 h1:oA75hJXpOd9SFgmNI2IAxWZkwzQPUDm7Jyyh3q489wM=
Expand Down Expand Up @@ -260,8 +252,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
Expand Down Expand Up @@ -434,7 +424,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down Expand Up @@ -548,7 +537,7 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
Expand All @@ -561,7 +550,7 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
Expand Down
131 changes: 131 additions & 0 deletions integration/evaluation_fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package integration_test

import (
"context"
"strings"
"testing"
"time"

flagd "github.com/open-feature/go-sdk-contrib/providers/flagd/pkg"
"github.com/open-feature/go-sdk/pkg/openfeature"
)

func setupFuzzClient(f *testing.F) *openfeature.Client {
f.Helper()

provider := flagd.NewProvider(flagd.WithPort(8013), flagd.WithoutCache())
openfeature.SetProvider(provider)

select {
case <-provider.IsReady():
case <-time.After(500 * time.Millisecond):
f.Fatal("provider not ready after 500 milliseconds")
}

return openfeature.NewClient("fuzzing")
}

func FuzzBooleanEvaluation(f *testing.F) {
client := setupFuzzClient(f)

f.Add("foo", false)
f.Add("FoO", true)
f.Add("FoO234", false)
f.Add("FoO2\b34", true)
f.Fuzz(func(t *testing.T, flagKey string, defaultValue bool) {
res, err := client.BooleanValueDetails(context.Background(), flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
if res.ErrorCode == openfeature.FlagNotFoundCode {
return
}
if strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) {
return
}
t.Error(err)
}
})
}

func FuzzStringEvaluation(f *testing.F) {
client := setupFuzzClient(f)

f.Add("foo", "bar")
f.Add("FoO", "BaR")
f.Add("FoO234", "Ba1232")
f.Add("FoO2\b34", "BaaR\b2312")
f.Fuzz(func(t *testing.T, flagKey string, defaultValue string) {
res, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
if res.ErrorCode == openfeature.FlagNotFoundCode {
return
}
if strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) {
return
}
t.Error(err)
}
})
}

func FuzzIntEvaluation(f *testing.F) {
client := setupFuzzClient(f)

f.Add("foo", int64(1))
f.Add("FoO", int64(99))
f.Add("FoO234", int64(100029))
f.Add("FoO2\b34", int64(-1))
f.Fuzz(func(t *testing.T, flagKey string, defaultValue int64) {
res, err := client.IntValueDetails(context.Background(), flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
if res.ErrorCode == openfeature.FlagNotFoundCode {
return
}
if strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) {
return
}
t.Error(err)
}
})
}

func FuzzFloatEvaluation(f *testing.F) {
client := setupFuzzClient(f)

f.Add("foo", float64(1))
f.Add("FoO", 99.9)
f.Add("FoO234", 0.00004)
f.Add("FoO2\b34", -1.9203)
f.Fuzz(func(t *testing.T, flagKey string, defaultValue float64) {
res, err := client.FloatValueDetails(context.Background(), flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
if res.ErrorCode == openfeature.FlagNotFoundCode {
return
}
if strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) {
return
}
t.Error(err)
}
})
}

func FuzzObjectEvaluation(f *testing.F) {
client := setupFuzzClient(f)

f.Add("foo", "{}")
f.Add("FoO", "true")
f.Add("FoO234", "-1.23")
f.Add("FoO2\b34", "1")
f.Fuzz(func(t *testing.T, flagKey string, defaultValue string) { // interface{} is not supported, using a string
res, err := client.ObjectValueDetails(context.Background(), flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
if res.ErrorCode == openfeature.FlagNotFoundCode {
return
}
if strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) {
return
}
t.Error(err)
}
})
}
20 changes: 13 additions & 7 deletions pkg/openfeature/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"sync"
"unicode/utf8"

"github.com/go-logr/logr"
)
Expand Down Expand Up @@ -563,6 +564,18 @@ func (c *Client) evaluate(
"evaluationContext", evalCtx, "evaluationOptions", options,
)

evalDetails := InterfaceEvaluationDetails{
Value: defaultValue,
EvaluationDetails: EvaluationDetails{
FlagKey: flag,
FlagType: flagType,
},
}

if !utf8.Valid([]byte(flag)) {
return evalDetails, NewParseErrorResolutionError("flag key is not a UTF-8 encoded string")
}
beeme1mr marked this conversation as resolved.
Show resolved Hide resolved

// ensure that the same provider & hooks are used across this transaction to avoid unexpected behaviour
api.RLock()
provider := api.prvder
Expand All @@ -583,13 +596,6 @@ func (c *Client) evaluate(
providerMetadata: provider.Metadata(),
evaluationContext: evalCtx,
}
evalDetails := InterfaceEvaluationDetails{
Value: defaultValue,
EvaluationDetails: EvaluationDetails{
FlagKey: flag,
FlagType: flagType,
},
}

defer func() {
c.finallyHooks(hookCtx, providerInvocationClientApiHooks, options)
Expand Down