Merge pull request #23302 from hashicorp/jbardin/provisioner-utf8

sanitize provisioner output strings
This commit is contained in:
James Bardin 2019-11-06 15:14:29 -05:00 committed by GitHub
commit 2d9d6d7afe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 133 additions and 2 deletions

View File

@ -2,6 +2,8 @@ package plugin
import ( import (
"log" "log"
"strings"
"unicode/utf8"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
proto "github.com/hashicorp/terraform/internal/tfplugin5" proto "github.com/hashicorp/terraform/internal/tfplugin5"
@ -90,7 +92,7 @@ type uiOutput struct {
func (o uiOutput) Output(s string) { func (o uiOutput) Output(s string) {
err := o.srv.Send(&proto.ProvisionResource_Response{ err := o.srv.Send(&proto.ProvisionResource_Response{
Output: s, Output: toValidUTF8(s, string(utf8.RuneError)),
}) })
if err != nil { if err != nil {
log.Printf("[ERROR] %s", err) log.Printf("[ERROR] %s", err)
@ -145,3 +147,55 @@ func (s *GRPCProvisionerServer) Stop(_ context.Context, req *proto.Stop_Request)
return resp, nil return resp, nil
} }
// FIXME: backported from go1.13 strings package, remove once terraform is
// using go >= 1.13
// ToValidUTF8 returns a copy of the string s with each run of invalid UTF-8 byte sequences
// replaced by the replacement string, which may be empty.
func toValidUTF8(s, replacement string) string {
var b strings.Builder
for i, c := range s {
if c != utf8.RuneError {
continue
}
_, wid := utf8.DecodeRuneInString(s[i:])
if wid == 1 {
b.Grow(len(s) + len(replacement))
b.WriteString(s[:i])
s = s[i:]
break
}
}
// Fast path for unchanged input
if b.Cap() == 0 { // didn't call b.Grow above
return s
}
invalid := false // previous byte was from an invalid UTF-8 sequence
for i := 0; i < len(s); {
c := s[i]
if c < utf8.RuneSelf {
i++
invalid = false
b.WriteByte(c)
continue
}
_, wid := utf8.DecodeRuneInString(s[i:])
if wid == 1 {
i++
if !invalid {
invalid = true
b.WriteString(replacement)
}
continue
}
invalid = false
b.WriteString(s[i : i+wid])
i += wid
}
return b.String()
}

View File

@ -1,5 +1,82 @@
package plugin package plugin
import proto "github.com/hashicorp/terraform/internal/tfplugin5" import (
"testing"
"unicode/utf8"
"github.com/golang/mock/gomock"
"github.com/hashicorp/terraform/helper/schema"
proto "github.com/hashicorp/terraform/internal/tfplugin5"
mockproto "github.com/hashicorp/terraform/plugin/mock_proto"
"github.com/hashicorp/terraform/terraform"
context "golang.org/x/net/context"
)
var _ proto.ProvisionerServer = (*GRPCProvisionerServer)(nil) var _ proto.ProvisionerServer = (*GRPCProvisionerServer)(nil)
type validUTF8Matcher string
func (m validUTF8Matcher) Matches(x interface{}) bool {
resp := x.(*proto.ProvisionResource_Response)
return utf8.Valid([]byte(resp.Output))
}
func (m validUTF8Matcher) String() string {
return string(m)
}
func mockProvisionerServer(t *testing.T, c *gomock.Controller) *mockproto.MockProvisioner_ProvisionResourceServer {
server := mockproto.NewMockProvisioner_ProvisionResourceServer(c)
server.EXPECT().Send(
validUTF8Matcher("check for valid utf8"),
).Return(nil)
return server
}
// ensure that a provsioner cannot return invalid utf8 which isn't allowed in
// the grpc protocol.
func TestProvisionerInvalidUTF8(t *testing.T) {
p := &schema.Provisioner{
ConnSchema: map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
Schema: map[string]*schema.Schema{
"foo": {
Type: schema.TypeInt,
Optional: true,
},
},
ApplyFunc: func(ctx context.Context) error {
out := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
out.Output("invalid \xc3\x28\n")
return nil
},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
srv := mockProvisionerServer(t, ctrl)
cfg := &proto.DynamicValue{
Msgpack: []byte("\x81\xa3foo\x01"),
}
conn := &proto.DynamicValue{
Msgpack: []byte("\x81\xa3foo\xa4host"),
}
provisionerServer := NewGRPCProvisionerServerShim(p)
req := &proto.ProvisionResource_Request{
Config: cfg,
Connection: conn,
}
if err := provisionerServer.ProvisionResource(req, srv); err != nil {
t.Fatal(err)
}
}