github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/fdpipe/pipe_test.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fdpipe
    16  
    17  import (
    18  	"bytes"
    19  	"io"
    20  	"os"
    21  	"testing"
    22  
    23  	"golang.org/x/sys/unix"
    24  	"github.com/SagerNet/gvisor/pkg/errors"
    25  	"github.com/SagerNet/gvisor/pkg/errors/linuxerr"
    26  	"github.com/SagerNet/gvisor/pkg/fd"
    27  	"github.com/SagerNet/gvisor/pkg/fdnotifier"
    28  	"github.com/SagerNet/gvisor/pkg/hostarch"
    29  	"github.com/SagerNet/gvisor/pkg/sentry/contexttest"
    30  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    31  	"github.com/SagerNet/gvisor/pkg/syserror"
    32  	"github.com/SagerNet/gvisor/pkg/usermem"
    33  )
    34  
    35  func singlePipeFD() (int, error) {
    36  	fds := make([]int, 2)
    37  	if err := unix.Pipe(fds); err != nil {
    38  		return -1, err
    39  	}
    40  	unix.Close(fds[1])
    41  	return fds[0], nil
    42  }
    43  
    44  func singleDirFD() (int, error) {
    45  	return unix.Open(os.TempDir(), unix.O_RDONLY, 0666)
    46  }
    47  
    48  func mockPipeDirent(t *testing.T) *fs.Dirent {
    49  	ctx := contexttest.Context(t)
    50  	node := fs.NewMockInodeOperations(ctx)
    51  	node.UAttr = fs.UnstableAttr{
    52  		Perms: fs.FilePermissions{
    53  			User: fs.PermMask{Read: true, Write: true},
    54  		},
    55  	}
    56  	inode := fs.NewInode(ctx, node, fs.NewMockMountSource(nil), fs.StableAttr{
    57  		Type:      fs.Pipe,
    58  		BlockSize: hostarch.PageSize,
    59  	})
    60  	return fs.NewDirent(ctx, inode, "")
    61  }
    62  
    63  func TestNewPipe(t *testing.T) {
    64  	for _, test := range []struct {
    65  		// desc is the test's description.
    66  		desc string
    67  
    68  		// getfd generates the fd to pass to newPipeOperations.
    69  		getfd func() (int, error)
    70  
    71  		// flags are the fs.FileFlags passed to newPipeOperations.
    72  		flags fs.FileFlags
    73  
    74  		// readAheadBuffer is the buffer passed to newPipeOperations.
    75  		readAheadBuffer []byte
    76  
    77  		// err is the expected error.
    78  		err error
    79  	}{
    80  		{
    81  			desc:  "Cannot make new pipe from bad fd",
    82  			getfd: func() (int, error) { return -1, nil },
    83  			err:   unix.EINVAL,
    84  		},
    85  		{
    86  			desc:  "Cannot make new pipe from non-pipe fd",
    87  			getfd: singleDirFD,
    88  			err:   unix.EINVAL,
    89  		},
    90  		{
    91  			desc:            "Can make new pipe from pipe fd",
    92  			getfd:           singlePipeFD,
    93  			flags:           fs.FileFlags{Read: true},
    94  			readAheadBuffer: []byte("hello"),
    95  		},
    96  	} {
    97  		gfd, err := test.getfd()
    98  		if err != nil {
    99  			t.Errorf("%s: getfd got (%d, %v), want (fd, nil)", test.desc, gfd, err)
   100  			continue
   101  		}
   102  		f := fd.New(gfd)
   103  
   104  		ctx := contexttest.Context(t)
   105  		p, err := newPipeOperations(ctx, nil, test.flags, f, test.readAheadBuffer)
   106  		if p != nil {
   107  			// This is necessary to remove the fd from the global fd notifier.
   108  			defer p.Release(ctx)
   109  		} else {
   110  			// If there is no p to DecRef on, because newPipeOperations failed, then the
   111  			// file still needs to be closed.
   112  			defer f.Close()
   113  		}
   114  
   115  		if err != test.err {
   116  			t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
   117  			continue
   118  		}
   119  		// Check the state of the pipe given that it was successfully opened.
   120  		if err == nil {
   121  			if p == nil {
   122  				t.Errorf("%s: got nil pipe and nil error, want (pipe, nil)", test.desc)
   123  				continue
   124  			}
   125  			if flags := p.flags; test.flags != flags {
   126  				t.Errorf("%s: got file flags %v, want %v", test.desc, flags, test.flags)
   127  				continue
   128  			}
   129  			if len(test.readAheadBuffer) != len(p.readAheadBuffer) {
   130  				t.Errorf("%s: got read ahead buffer length %d, want %d", test.desc, len(p.readAheadBuffer), len(test.readAheadBuffer))
   131  				continue
   132  			}
   133  			fileFlags, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(p.file.FD()), unix.F_GETFL, 0)
   134  			if errno != 0 {
   135  				t.Errorf("%s: failed to get file flags for fd %d, got %v, want 0", test.desc, p.file.FD(), errno)
   136  				continue
   137  			}
   138  			if fileFlags&unix.O_NONBLOCK == 0 {
   139  				t.Errorf("%s: pipe is blocking, expected non-blocking", test.desc)
   140  				continue
   141  			}
   142  			if !fdnotifier.HasFD(int32(f.FD())) {
   143  				t.Errorf("%s: pipe fd %d is not registered for events", test.desc, f.FD())
   144  			}
   145  		}
   146  	}
   147  }
   148  
   149  func TestPipeDestruction(t *testing.T) {
   150  	fds := make([]int, 2)
   151  	if err := unix.Pipe(fds); err != nil {
   152  		t.Fatalf("failed to create pipes: got %v, want nil", err)
   153  	}
   154  	f := fd.New(fds[0])
   155  
   156  	// We don't care about the other end, just use the read end.
   157  	unix.Close(fds[1])
   158  
   159  	// Test the read end, but it doesn't really matter which.
   160  	ctx := contexttest.Context(t)
   161  	p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, f, nil)
   162  	if err != nil {
   163  		f.Close()
   164  		t.Fatalf("newPipeOperations got error %v, want nil", err)
   165  	}
   166  	// Drop our only reference, which should trigger the destructor.
   167  	p.Release(ctx)
   168  
   169  	if fdnotifier.HasFD(int32(fds[0])) {
   170  		t.Fatalf("after DecRef fdnotifier has fd %d, want no longer registered", fds[0])
   171  	}
   172  	if p.file != nil {
   173  		t.Errorf("after DecRef got file, want nil")
   174  	}
   175  }
   176  
   177  type Seek struct{}
   178  
   179  type ReadDir struct{}
   180  
   181  type Writev struct {
   182  	Src usermem.IOSequence
   183  }
   184  
   185  type Readv struct {
   186  	Dst usermem.IOSequence
   187  }
   188  
   189  type Fsync struct{}
   190  
   191  func TestPipeRequest(t *testing.T) {
   192  	for _, test := range []struct {
   193  		// desc is the test's description.
   194  		desc string
   195  
   196  		// request to execute.
   197  		context interface{}
   198  
   199  		// flags determines whether to use the read or write end
   200  		// of the pipe, for this test it can only be Read or Write.
   201  		flags fs.FileFlags
   202  
   203  		// keepOpenPartner if false closes the other end of the pipe,
   204  		// otherwise this is delayed until the end of the test.
   205  		keepOpenPartner bool
   206  
   207  		// expected error
   208  		err error
   209  	}{
   210  		{
   211  			desc:    "ReadDir on pipe returns ENOTDIR",
   212  			context: &ReadDir{},
   213  			err:     linuxerr.ENOTDIR,
   214  		},
   215  		{
   216  			desc:    "Fsync on pipe returns EINVAL",
   217  			context: &Fsync{},
   218  			err:     linuxerr.EINVAL,
   219  		},
   220  		{
   221  			desc:    "Seek on pipe returns ESPIPE",
   222  			context: &Seek{},
   223  			err:     linuxerr.ESPIPE,
   224  		},
   225  		{
   226  			desc:    "Readv on pipe from empty buffer returns nil",
   227  			context: &Readv{Dst: usermem.BytesIOSequence(nil)},
   228  			flags:   fs.FileFlags{Read: true},
   229  		},
   230  		{
   231  			desc:    "Readv on pipe from non-empty buffer and closed partner returns EOF",
   232  			context: &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))},
   233  			flags:   fs.FileFlags{Read: true},
   234  			err:     io.EOF,
   235  		},
   236  		{
   237  			desc:            "Readv on pipe from non-empty buffer and open partner returns EWOULDBLOCK",
   238  			context:         &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))},
   239  			flags:           fs.FileFlags{Read: true},
   240  			keepOpenPartner: true,
   241  			err:             syserror.ErrWouldBlock,
   242  		},
   243  		{
   244  			desc:    "Writev on pipe from empty buffer returns nil",
   245  			context: &Writev{Src: usermem.BytesIOSequence(nil)},
   246  			flags:   fs.FileFlags{Write: true},
   247  		},
   248  		{
   249  			desc:    "Writev on pipe from non-empty buffer and closed partner returns EPIPE",
   250  			context: &Writev{Src: usermem.BytesIOSequence([]byte("hello"))},
   251  			flags:   fs.FileFlags{Write: true},
   252  			err:     linuxerr.EPIPE,
   253  		},
   254  		{
   255  			desc:            "Writev on pipe from non-empty buffer and open partner succeeds",
   256  			context:         &Writev{Src: usermem.BytesIOSequence([]byte("hello"))},
   257  			flags:           fs.FileFlags{Write: true},
   258  			keepOpenPartner: true,
   259  		},
   260  	} {
   261  		if test.flags.Read && test.flags.Write {
   262  			panic("both read and write not supported for this test")
   263  		}
   264  
   265  		fds := make([]int, 2)
   266  		if err := unix.Pipe(fds); err != nil {
   267  			t.Errorf("%s: failed to create pipes: got %v, want nil", test.desc, err)
   268  			continue
   269  		}
   270  
   271  		// Configure the fd and partner fd based on the file flags.
   272  		testFd, partnerFd := fds[0], fds[1]
   273  		if test.flags.Write {
   274  			testFd, partnerFd = fds[1], fds[0]
   275  		}
   276  
   277  		// Configure closing the fds.
   278  		if test.keepOpenPartner {
   279  			defer unix.Close(partnerFd)
   280  		} else {
   281  			unix.Close(partnerFd)
   282  		}
   283  
   284  		// Create the pipe.
   285  		ctx := contexttest.Context(t)
   286  		p, err := newPipeOperations(ctx, nil, test.flags, fd.New(testFd), nil)
   287  		if err != nil {
   288  			t.Fatalf("%s: newPipeOperations got error %v, want nil", test.desc, err)
   289  		}
   290  		defer p.Release(ctx)
   291  
   292  		inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Pipe})
   293  		file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
   294  
   295  		// Issue request via the appropriate function.
   296  		switch c := test.context.(type) {
   297  		case *Seek:
   298  			_, err = p.Seek(ctx, file, 0, 0)
   299  		case *ReadDir:
   300  			_, err = p.Readdir(ctx, file, nil)
   301  		case *Readv:
   302  			_, err = p.Read(ctx, file, c.Dst, 0)
   303  		case *Writev:
   304  			_, err = p.Write(ctx, file, c.Src, 0)
   305  		case *Fsync:
   306  			err = p.Fsync(ctx, file, 0, fs.FileMaxOffset, fs.SyncAll)
   307  		default:
   308  			t.Errorf("%s: unknown request type %T", test.desc, test.context)
   309  		}
   310  
   311  		if linuxErr, ok := test.err.(*errors.Error); ok {
   312  			if !linuxerr.Equals(linuxErr, unwrapError(err)) {
   313  				t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
   314  			}
   315  		} else if test.err != unwrapError(err) {
   316  			t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
   317  		}
   318  	}
   319  }
   320  
   321  func TestPipeReadAheadBuffer(t *testing.T) {
   322  	fds := make([]int, 2)
   323  	if err := unix.Pipe(fds); err != nil {
   324  		t.Fatalf("failed to create pipes: got %v, want nil", err)
   325  	}
   326  	rfile := fd.New(fds[0])
   327  
   328  	// Eventually close the write end, which is not wrapped in a pipe object.
   329  	defer unix.Close(fds[1])
   330  
   331  	// Write some bytes to this end.
   332  	data := []byte("world")
   333  	if n, err := unix.Write(fds[1], data); n != len(data) || err != nil {
   334  		rfile.Close()
   335  		t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data))
   336  	}
   337  	// Close the write end immediately, we don't care about it.
   338  
   339  	buffered := []byte("hello ")
   340  	ctx := contexttest.Context(t)
   341  	p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, buffered)
   342  	if err != nil {
   343  		rfile.Close()
   344  		t.Fatalf("newPipeOperations got error %v, want nil", err)
   345  	}
   346  	defer p.Release(ctx)
   347  
   348  	inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
   349  		Type: fs.Pipe,
   350  	})
   351  	file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
   352  
   353  	// In total we expect to read data + buffered.
   354  	total := append(buffered, data...)
   355  
   356  	buf := make([]byte, len(total))
   357  	iov := usermem.BytesIOSequence(buf)
   358  	n, err := p.Read(contexttest.Context(t), file, iov, 0)
   359  	if err != nil {
   360  		t.Fatalf("read request got error %v, want nil", err)
   361  	}
   362  	if n != int64(len(total)) {
   363  		t.Fatalf("read request got %d bytes, want %d", n, len(total))
   364  	}
   365  	if !bytes.Equal(buf, total) {
   366  		t.Errorf("read request got bytes [%v], want [%v]", buf, total)
   367  	}
   368  }
   369  
   370  // This is very important for pipes in general because they can return
   371  // EWOULDBLOCK and for those that block they must continue until they have read
   372  // all of the data (and report it as such).
   373  func TestPipeReadsAccumulate(t *testing.T) {
   374  	fds := make([]int, 2)
   375  	if err := unix.Pipe(fds); err != nil {
   376  		t.Fatalf("failed to create pipes: got %v, want nil", err)
   377  	}
   378  	rfile := fd.New(fds[0])
   379  
   380  	// Eventually close the write end, it doesn't depend on a pipe object.
   381  	defer unix.Close(fds[1])
   382  
   383  	// Get a new read only pipe reference.
   384  	ctx := contexttest.Context(t)
   385  	p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, nil)
   386  	if err != nil {
   387  		rfile.Close()
   388  		t.Fatalf("newPipeOperations got error %v, want nil", err)
   389  	}
   390  	// Don't forget to remove the fd from the fd notifier.  Otherwise other tests will
   391  	// likely be borked, because it's global :(
   392  	defer p.Release(ctx)
   393  
   394  	inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
   395  		Type: fs.Pipe,
   396  	})
   397  	file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
   398  
   399  	// Write some some bytes to the pipe.
   400  	data := []byte("some message")
   401  	if n, err := unix.Write(fds[1], data); n != len(data) || err != nil {
   402  		t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data))
   403  	}
   404  
   405  	// Construct a segment vec that is a bit more than we have written so we
   406  	// trigger an EWOULDBLOCK.
   407  	wantBytes := len(data) + 1
   408  	readBuffer := make([]byte, wantBytes)
   409  	iov := usermem.BytesIOSequence(readBuffer)
   410  	n, err := p.Read(ctx, file, iov, 0)
   411  	total := n
   412  	iov = iov.DropFirst64(n)
   413  	if err != syserror.ErrWouldBlock {
   414  		t.Fatalf("Readv got error %v, want %v", err, syserror.ErrWouldBlock)
   415  	}
   416  
   417  	// Write a few more bytes to allow us to read more/accumulate.
   418  	extra := []byte("extra")
   419  	if n, err := unix.Write(fds[1], extra); n != len(extra) || err != nil {
   420  		t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(extra))
   421  	}
   422  
   423  	// This time, using the same request, we should not block.
   424  	n, err = p.Read(ctx, file, iov, 0)
   425  	total += n
   426  	if err != nil {
   427  		t.Fatalf("Readv got error %v, want nil", err)
   428  	}
   429  
   430  	// Assert that the result we got back is cumulative.
   431  	if total != int64(wantBytes) {
   432  		t.Fatalf("Readv sequence got %d bytes, want %d", total, wantBytes)
   433  	}
   434  
   435  	if want := append(data, extra[0]); !bytes.Equal(readBuffer, want) {
   436  		t.Errorf("Readv sequence got %v, want %v", readBuffer, want)
   437  	}
   438  }
   439  
   440  // Same as TestReadsAccumulate.
   441  func TestPipeWritesAccumulate(t *testing.T) {
   442  	fds := make([]int, 2)
   443  	if err := unix.Pipe(fds); err != nil {
   444  		t.Fatalf("failed to create pipes: got %v, want nil", err)
   445  	}
   446  	wfile := fd.New(fds[1])
   447  
   448  	// Eventually close the read end, it doesn't depend on a pipe object.
   449  	defer unix.Close(fds[0])
   450  
   451  	// Get a new write only pipe reference.
   452  	ctx := contexttest.Context(t)
   453  	p, err := newPipeOperations(ctx, nil, fs.FileFlags{Write: true}, wfile, nil)
   454  	if err != nil {
   455  		wfile.Close()
   456  		t.Fatalf("newPipeOperations got error %v, want nil", err)
   457  	}
   458  	// Don't forget to remove the fd from the fd notifier. Otherwise other tests
   459  	// will likely be borked, because it's global :(
   460  	defer p.Release(ctx)
   461  
   462  	inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
   463  		Type: fs.Pipe,
   464  	})
   465  	file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
   466  
   467  	pipeSize, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(wfile.FD()), unix.F_GETPIPE_SZ, 0)
   468  	if errno != 0 {
   469  		t.Fatalf("fcntl(F_GETPIPE_SZ) failed: %v", errno)
   470  	}
   471  	t.Logf("Pipe buffer size: %d", pipeSize)
   472  
   473  	// Construct a segment vec that is larger than the pipe size to trigger an
   474  	// EWOULDBLOCK.
   475  	wantBytes := int(pipeSize) * 2
   476  	writeBuffer := make([]byte, wantBytes)
   477  	for i := 0; i < wantBytes; i++ {
   478  		writeBuffer[i] = 'a'
   479  	}
   480  	iov := usermem.BytesIOSequence(writeBuffer)
   481  	n, err := p.Write(ctx, file, iov, 0)
   482  	if err != syserror.ErrWouldBlock {
   483  		t.Fatalf("Writev got error %v, want %v", err, syserror.ErrWouldBlock)
   484  	}
   485  	if n != int64(pipeSize) {
   486  		t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize)
   487  	}
   488  	total := n
   489  	iov = iov.DropFirst64(n)
   490  
   491  	// Read the entire pipe buf size to make space for the second half.
   492  	readBuffer := make([]byte, n)
   493  	if n, err := unix.Read(fds[0], readBuffer); n != len(readBuffer) || err != nil {
   494  		t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(readBuffer))
   495  	}
   496  	if !bytes.Equal(readBuffer, writeBuffer[:len(readBuffer)]) {
   497  		t.Fatalf("wrong data read from pipe, got: %v, want: %v", readBuffer, writeBuffer)
   498  	}
   499  
   500  	// This time we should not block.
   501  	n, err = p.Write(ctx, file, iov, 0)
   502  	if err != nil {
   503  		t.Fatalf("Writev got error %v, want nil", err)
   504  	}
   505  	if n != int64(pipeSize) {
   506  		t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize)
   507  	}
   508  	total += n
   509  
   510  	// Assert that the result we got back is cumulative.
   511  	if total != int64(wantBytes) {
   512  		t.Fatalf("Writev sequence got %d bytes, want %d", total, wantBytes)
   513  	}
   514  }