Skip to content

Commit 25be5d9

Browse files
torsmcopybara-github
authored andcommitted
Harden StdinService configs
Make StdinService configs explicitly opt into accepting args or stdin by the incoming InputMessages. Because there are no known users of StdinService, this should not break any existing configs. PiperOrigin-RevId: 704641242
1 parent ceb2de0 commit 25be5d9

File tree

4 files changed

+118
-130
lines changed

4 files changed

+118
-130
lines changed

fleetspeak/src/client/stdinservice/proto/fleetspeak_stdinservice/config.pb.go

Lines changed: 36 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fleetspeak/src/client/stdinservice/proto/fleetspeak_stdinservice/config.proto

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@ package fleetspeak.stdinservice;
44

55
option go_package = "github.com/google/fleetspeak/fleetspeak/src/client/stdinservice/proto/fleetspeak_stdinservice";
66

7-
// The configuration information expected by stdinservice.Factory
8-
// in ClientServiceConfig.config.
7+
// The configuration information expected by stdinservice.Factory in
8+
// ClientServiceConfig.config.
99
message Config {
10+
// The path of the executable to run.
1011
string cmd = 1;
12+
13+
// If true, the command will be ran with arguments provided by the
14+
// InputMessage.
15+
bool accept_args = 2;
16+
17+
// If true, the command will be ran with the input provided by the
18+
// InputMessage.
19+
bool accept_stdin = 3;
1120
}

fleetspeak/src/client/stdinservice/stdinservice.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,14 @@ func (s *StdinService) ProcessMessage(ctx context.Context, m *fspb.Message) erro
8181

8282
var stdout, stderr bytes.Buffer
8383

84-
cmd := exec.CommandContext(ctx, s.ssConf.Cmd, im.Args...)
85-
cmd.Stdin = bytes.NewBuffer(im.Input)
84+
var args []string
85+
if s.ssConf.AcceptArgs {
86+
args = im.Args
87+
}
88+
cmd := exec.CommandContext(ctx, s.ssConf.Cmd, args...)
89+
if s.ssConf.AcceptStdin {
90+
cmd.Stdin = bytes.NewBuffer(im.Input)
91+
}
8692
cmd.Stdout = &stdout
8793
cmd.Stderr = &stderr
8894

fleetspeak/src/client/stdinservice/stdinservice_test.go

Lines changed: 63 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"bytes"
1919
"context"
2020
"fmt"
21+
"runtime"
2122
"strings"
2223
"testing"
2324

@@ -28,12 +29,11 @@ import (
2829
fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak"
2930
)
3031

31-
func TestStdinServiceWithEcho(t *testing.T) {
32+
func mustProcessStdinService(t *testing.T, conf *sspb.Config, im *sspb.InputMessage) *sspb.OutputMessage {
33+
t.Helper()
3234
s, err := Factory(&fspb.ClientServiceConfig{
33-
Name: "EchoService",
34-
Config: anypbtest.New(t, &sspb.Config{
35-
Cmd: "python",
36-
}),
35+
Name: "TestService",
36+
Config: anypbtest.New(t, conf),
3737
})
3838
if err != nil {
3939
t.Fatal(err)
@@ -50,9 +50,7 @@ func TestStdinServiceWithEcho(t *testing.T) {
5050
err = s.ProcessMessage(context.Background(),
5151
&fspb.Message{
5252
MessageType: "StdinServiceInputMessage",
53-
Data: anypbtest.New(t, &sspb.InputMessage{
54-
Args: []string{"-c", `print("foo bar")`},
55-
}),
53+
Data: anypbtest.New(t, im),
5654
})
5755
if err != nil {
5856
t.Fatal(err)
@@ -62,150 +60,99 @@ func TestStdinServiceWithEcho(t *testing.T) {
6260
select {
6361
case output = <-outChan:
6462
default:
65-
t.Fatal(".ProcessMessage (/bin/echo foo bar) expected to produce message, but none found")
63+
t.Fatal("ProcessMessage() expected to produce message, but none found")
6664
}
6765

6866
om := &sspb.OutputMessage{}
6967
if err := output.Data.UnmarshalTo(om); err != nil {
7068
t.Fatal(err)
7169
}
70+
return om
71+
}
7272

73-
wantStdout := []byte("foo bar\n")
74-
wantStdoutWin := []byte("foo bar\r\n")
75-
if !bytes.Equal(om.Stdout, wantStdout) &&
76-
!bytes.Equal(om.Stdout, wantStdoutWin) {
77-
t.Fatalf("unexpected output; got %q, want %q", om.Stdout, wantStdout)
73+
func cmpStdout(got []byte, want string) bool {
74+
if runtime.GOOS == "windows" {
75+
want = strings.ReplaceAll(want, "\n", "\r\n")
7876
}
77+
return bytes.Equal(got, []byte(want))
7978
}
8079

81-
func TestStdinServiceWithCat(t *testing.T) {
82-
s, err := Factory(&fspb.ClientServiceConfig{
83-
Name: "CatService",
84-
Config: anypbtest.New(t, &sspb.Config{
85-
Cmd: "python",
86-
}),
87-
})
88-
if err != nil {
89-
t.Fatal(err)
80+
func TestStdinService_AcceptsArgs(t *testing.T) {
81+
conf := &sspb.Config{
82+
Cmd: "echo",
83+
AcceptArgs: true,
84+
AcceptStdin: true,
9085
}
91-
92-
outChan := make(chan *fspb.Message, 1)
93-
err = s.Start(&clitesting.MockServiceContext{
94-
OutChan: outChan,
95-
})
96-
if err != nil {
97-
t.Fatal(err)
86+
im := &sspb.InputMessage{
87+
Args: []string{"foo bar"},
9888
}
9989

100-
err = s.ProcessMessage(context.Background(),
101-
&fspb.Message{
102-
MessageType: "StdinServiceInputMessage",
103-
Data: anypbtest.New(t, &sspb.InputMessage{
104-
Args: []string{"-c", `
105-
try:
106-
my_input = raw_input # Python2 compat
107-
except NameError:
108-
my_input = input
109-
110-
try:
111-
while True:
112-
print(my_input())
113-
except EOFError:
114-
pass
115-
`},
116-
Input: []byte("foo bar"),
117-
}),
118-
})
119-
if err != nil {
120-
t.Fatalf("s.ProcessMessage(...) = %q, want success", err)
90+
om := mustProcessStdinService(t, conf, im)
91+
wantStdout := "foo bar\n"
92+
if !cmpStdout(om.Stdout, wantStdout) {
93+
t.Fatalf("unexpected output; got %q, want %q", om.Stdout, wantStdout)
12194
}
95+
}
12296

123-
var output *fspb.Message
124-
select {
125-
case output = <-outChan:
126-
default:
127-
t.Fatal(".ProcessMessage (/bin/cat <<< 'foo bar') expected to produce message, but none found")
97+
func TestStdinService_AcceptsStdin(t *testing.T) {
98+
conf := &sspb.Config{
99+
Cmd: "cat",
100+
AcceptArgs: true,
101+
AcceptStdin: true,
128102
}
129-
130-
om := &sspb.OutputMessage{}
131-
if err := output.Data.UnmarshalTo(om); err != nil {
132-
t.Fatal(err)
103+
im := &sspb.InputMessage{
104+
Input: []byte("foo bar"),
133105
}
134106

135-
wantStdout := []byte("foo bar\n")
136-
wantStdoutWin := []byte("foo bar\r\n")
137-
if !bytes.Equal(om.Stdout, wantStdout) &&
138-
!bytes.Equal(om.Stdout, wantStdoutWin) {
107+
om := mustProcessStdinService(t, conf, im)
108+
109+
wantStdout := "foo bar"
110+
if !cmpStdout(om.Stdout, wantStdout) {
139111
t.Fatalf("unexpected output; got %q, want %q", om.Stdout, wantStdout)
140112
}
141113
}
142114

143-
func TestStdinServiceReportsResourceUsage(t *testing.T) {
144-
s, err := Factory(&fspb.ClientServiceConfig{
145-
Name: "BashService",
146-
Config: anypbtest.New(t, &sspb.Config{
147-
Cmd: "python",
148-
}),
149-
})
150-
if err != nil {
151-
t.Fatal(err)
115+
func TestStdinService_RejectsArgs(t *testing.T) {
116+
conf := &sspb.Config{
117+
Cmd: "echo",
118+
AcceptArgs: false,
119+
AcceptStdin: false,
152120
}
153-
154-
outChan := make(chan *fspb.Message, 1)
155-
err = s.Start(&clitesting.MockServiceContext{
156-
OutChan: outChan,
157-
})
158-
if err != nil {
159-
t.Fatal(err)
121+
im := &sspb.InputMessage{
122+
Args: []string{"don't print this"},
160123
}
124+
om := mustProcessStdinService(t, conf, im)
161125

162-
err = s.ProcessMessage(context.Background(),
163-
&fspb.Message{
164-
MessageType: "StdinServiceInputMessage",
165-
Data: anypbtest.New(t, &sspb.InputMessage{
166-
// Generate some system (os.listdir) and user (everything else) execution time...
167-
Args: []string{"-c", `
168-
import os
169-
import time
170-
171-
t0 = time.time()
172-
while time.time() - t0 < 1.:
173-
os.listdir(".")
174-
`},
175-
}),
176-
})
177-
if err != nil {
178-
t.Fatal(err)
126+
wantStdout := "\n"
127+
if !cmpStdout(om.Stdout, wantStdout) {
128+
t.Fatalf("unexpected output; got %q, want %q", om.Stdout, wantStdout)
179129
}
130+
}
180131

181-
var output *fspb.Message
182-
select {
183-
case output = <-outChan:
184-
default:
185-
t.Fatal(".ProcessMessage (/bin/bash ...) expected to produce message, but none found")
132+
func TestStdinService_RejectsStdin(t *testing.T) {
133+
conf := &sspb.Config{
134+
Cmd: "cat",
135+
AcceptArgs: false,
136+
AcceptStdin: false,
186137
}
187-
188-
om := &sspb.OutputMessage{}
189-
if err := output.Data.UnmarshalTo(om); err != nil {
190-
t.Fatal(err)
138+
im := &sspb.InputMessage{
139+
Input: []byte("don't print this"),
191140
}
141+
om := mustProcessStdinService(t, conf, im)
192142

193-
// We don't test for ResourceUsage.MeanResidentMemory because memory is currently not being
194-
// queried after the process has terminated. It's only queried right after launching the command
195-
// in which case it can be recorded as "0" which would be indistinguishable from it not being set
196-
// at all, resulting in a flaky test case. The fact that the other resource usage metrics have
197-
// been set here is good enough for now.
198-
199-
if om.Timestamp.Seconds <= 0 {
200-
t.Fatalf("unexpected output; StdinServiceOutputMessage.timestamp.seconds not set: %q", om)
143+
wantStdout := ""
144+
if !cmpStdout(om.Stdout, wantStdout) {
145+
t.Fatalf("unexpected output; got %q, want %q", om.Stdout, wantStdout)
201146
}
202147
}
203148

204-
func TestStdinServiceCancellation(t *testing.T) {
149+
func TestStdinService_Cancelation(t *testing.T) {
205150
s, err := Factory(&fspb.ClientServiceConfig{
206151
Name: "SleepService",
207152
Config: anypbtest.New(t, &sspb.Config{
208-
Cmd: "python",
153+
Cmd: "python",
154+
AcceptArgs: true,
155+
AcceptStdin: true,
209156
}),
210157
})
211158
if err != nil {

0 commit comments

Comments
 (0)