forked from containers/common
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pkg/detach: fix broken Copy() detach sequence
The code only could handle the detach sequence when it read one byte at a time. This obviously is not correct and lead to some issues for my automated test in my podman PR[1] where I added some automated tests for detaching and the read part is really undefined and depends on the input side/kernel scheduling on how much we read at once. This is large rework to make the code check for the key sequence across the entire buffer. That is of course more work but it needs to happen for this to work correctly. I guess the only reason why this was never noticed is because normally user detach manually by typing and not in an automated way which is much slower and thus likely reads the bytes one by one. I added new test to actually confirm the behavior. And to ensure this works with various read sizes I made it a fuzz test. I had this running for a while and did not spot any issues there. The old code fails already on the simple test cases. [1] containers/podman#25083 Signed-off-by: Paul Holzinger <[email protected]>
- Loading branch information
Showing
2 changed files
with
183 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package detach | ||
|
||
import ( | ||
"bytes" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var ( | ||
smallBytes = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} | ||
bigBytes = []byte(strings.Repeat("0F", 32*1024+30)) | ||
) | ||
|
||
func newCustomReader(buf *bytes.Buffer, readsize uint) *customReader { | ||
return &customReader{ | ||
inner: buf, | ||
readsize: readsize, | ||
} | ||
} | ||
|
||
type customReader struct { | ||
inner *bytes.Buffer | ||
readsize uint | ||
} | ||
|
||
func (c *customReader) Read(p []byte) (n int, err error) { | ||
return c.inner.Read(p[:min(int(c.readsize), len(p))]) | ||
} | ||
|
||
func FuzzCopy(f *testing.F) { | ||
for _, i := range []uint{1, 2, 3, 5, 10, 100, 200, 1000, 1024, 32 * 1024} { | ||
f.Add(i) | ||
} | ||
|
||
f.Fuzz(func(t *testing.T, readSize uint) { | ||
// 0 is not a valid read size | ||
if readSize == 0 { | ||
t.Skip() | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
from []byte | ||
expected []byte | ||
expectDetach bool | ||
keys []byte | ||
}{ | ||
{ | ||
name: "small copy", | ||
from: smallBytes, | ||
expected: smallBytes, | ||
keys: nil, | ||
}, | ||
{ | ||
name: "small copy with detach keys", | ||
from: smallBytes, | ||
expected: smallBytes, | ||
keys: []byte{'A', 'B'}, | ||
}, | ||
{ | ||
name: "big copy", | ||
from: bigBytes, | ||
expected: bigBytes, | ||
keys: nil, | ||
}, | ||
{ | ||
name: "big copy with detach keys", | ||
from: bigBytes, | ||
expected: bigBytes, | ||
keys: []byte{'A', 'B'}, | ||
}, | ||
{ | ||
name: "simple detach 1 key", | ||
from: append(smallBytes, 'A'), | ||
expected: smallBytes, | ||
expectDetach: true, | ||
keys: []byte{'A'}, | ||
}, | ||
{ | ||
name: "simple detach 2 keys", | ||
from: append(smallBytes, 'A', 'B'), | ||
expected: smallBytes, | ||
expectDetach: true, | ||
keys: []byte{'A', 'B'}, | ||
}, | ||
{ | ||
name: "detach early", | ||
from: append(smallBytes, 'A', 'B', 'B', 'A'), | ||
expected: smallBytes, | ||
expectDetach: true, | ||
keys: []byte{'A', 'B'}, | ||
}, | ||
{ | ||
name: "detach with partial match", | ||
from: append(smallBytes, 'A', 'A', 'A', 'B'), | ||
expected: append(smallBytes, 'A', 'A'), | ||
expectDetach: true, | ||
keys: []byte{'A', 'B'}, | ||
}, | ||
{ | ||
name: "big buffer detach with partial match", | ||
from: append(bigBytes, 'A', 'A', 'A', 'B'), | ||
expected: append(bigBytes, 'A', 'A'), | ||
expectDetach: true, | ||
keys: []byte{'A', 'B'}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
dst := &bytes.Buffer{} | ||
src := newCustomReader(bytes.NewBuffer(tt.from), readSize) | ||
written, err := Copy(dst, src, tt.keys) | ||
if tt.expectDetach { | ||
assert.ErrorIs(t, err, ErrDetach) | ||
} else { | ||
assert.NoError(t, err) | ||
} | ||
assert.Equal(t, dst.Len(), int(written), "bytes written matches buffer") | ||
assert.Equal(t, tt.expected, dst.Bytes(), "buffer matches") | ||
}) | ||
} | ||
}) | ||
} |