github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/fdpipe/pipe_opener_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  	"fmt"
    20  	"io"
    21  	"os"
    22  	"path"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/google/uuid"
    27  	"golang.org/x/sys/unix"
    28  	"github.com/SagerNet/gvisor/pkg/context"
    29  	"github.com/SagerNet/gvisor/pkg/errors/linuxerr"
    30  	"github.com/SagerNet/gvisor/pkg/fd"
    31  	"github.com/SagerNet/gvisor/pkg/sentry/contexttest"
    32  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    33  	"github.com/SagerNet/gvisor/pkg/syserror"
    34  	"github.com/SagerNet/gvisor/pkg/usermem"
    35  )
    36  
    37  type hostOpener struct {
    38  	name string
    39  }
    40  
    41  func (h *hostOpener) NonBlockingOpen(_ context.Context, p fs.PermMask) (*fd.FD, error) {
    42  	var flags int
    43  	switch {
    44  	case p.Read && p.Write:
    45  		flags = unix.O_RDWR
    46  	case p.Write:
    47  		flags = unix.O_WRONLY
    48  	case p.Read:
    49  		flags = unix.O_RDONLY
    50  	default:
    51  		return nil, unix.EINVAL
    52  	}
    53  	f, err := unix.Open(h.name, flags|unix.O_NONBLOCK, 0666)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	return fd.New(f), nil
    58  }
    59  
    60  func pipename() string {
    61  	return fmt.Sprintf(path.Join(os.TempDir(), "test-named-pipe-%s"), uuid.New())
    62  }
    63  
    64  func mkpipe(name string) error {
    65  	return unix.Mknod(name, unix.S_IFIFO|0666, 0)
    66  }
    67  
    68  func TestTryOpen(t *testing.T) {
    69  	for _, test := range []struct {
    70  		// desc is the test's description.
    71  		desc string
    72  
    73  		// makePipe is true if the test case should create the pipe.
    74  		makePipe bool
    75  
    76  		// flags are the fs.FileFlags used to open the pipe.
    77  		flags fs.FileFlags
    78  
    79  		// expectFile is true if a fs.File is expected.
    80  		expectFile bool
    81  
    82  		// err is the expected error
    83  		err error
    84  	}{
    85  		{
    86  			desc:       "FileFlags lacking Read and Write are invalid",
    87  			makePipe:   false,
    88  			flags:      fs.FileFlags{}, /* bogus */
    89  			expectFile: false,
    90  			err:        unix.EINVAL,
    91  		},
    92  		{
    93  			desc:       "NonBlocking Read only error returns immediately",
    94  			makePipe:   false, /* causes the error */
    95  			flags:      fs.FileFlags{Read: true, NonBlocking: true},
    96  			expectFile: false,
    97  			err:        unix.ENOENT,
    98  		},
    99  		{
   100  			desc:       "NonBlocking Read only success returns immediately",
   101  			makePipe:   true,
   102  			flags:      fs.FileFlags{Read: true, NonBlocking: true},
   103  			expectFile: true,
   104  			err:        nil,
   105  		},
   106  		{
   107  			desc:       "NonBlocking Write only error returns immediately",
   108  			makePipe:   false, /* causes the error */
   109  			flags:      fs.FileFlags{Write: true, NonBlocking: true},
   110  			expectFile: false,
   111  			err:        unix.ENOENT,
   112  		},
   113  		{
   114  			desc:       "NonBlocking Write only no reader error returns immediately",
   115  			makePipe:   true,
   116  			flags:      fs.FileFlags{Write: true, NonBlocking: true},
   117  			expectFile: false,
   118  			err:        unix.ENXIO,
   119  		},
   120  		{
   121  			desc:       "ReadWrite error returns immediately",
   122  			makePipe:   false, /* causes the error */
   123  			flags:      fs.FileFlags{Read: true, Write: true},
   124  			expectFile: false,
   125  			err:        unix.ENOENT,
   126  		},
   127  		{
   128  			desc:       "ReadWrite returns immediately",
   129  			makePipe:   true,
   130  			flags:      fs.FileFlags{Read: true, Write: true},
   131  			expectFile: true,
   132  			err:        nil,
   133  		},
   134  		{
   135  			desc:       "Blocking Write only returns open error",
   136  			makePipe:   false, /* causes the error */
   137  			flags:      fs.FileFlags{Write: true},
   138  			expectFile: false,
   139  			err:        unix.ENOENT, /* from bogus perms */
   140  		},
   141  		{
   142  			desc:       "Blocking Read only returns open error",
   143  			makePipe:   false, /* causes the error */
   144  			flags:      fs.FileFlags{Read: true},
   145  			expectFile: false,
   146  			err:        unix.ENOENT,
   147  		},
   148  		{
   149  			desc:       "Blocking Write only returns with syserror.ErrWouldBlock",
   150  			makePipe:   true,
   151  			flags:      fs.FileFlags{Write: true},
   152  			expectFile: false,
   153  			err:        syserror.ErrWouldBlock,
   154  		},
   155  		{
   156  			desc:       "Blocking Read only returns with syserror.ErrWouldBlock",
   157  			makePipe:   true,
   158  			flags:      fs.FileFlags{Read: true},
   159  			expectFile: false,
   160  			err:        syserror.ErrWouldBlock,
   161  		},
   162  	} {
   163  		name := pipename()
   164  		if test.makePipe {
   165  			// Create the pipe.  We do this per-test case to keep tests independent.
   166  			if err := mkpipe(name); err != nil {
   167  				t.Errorf("%s: failed to make host pipe: %v", test.desc, err)
   168  				continue
   169  			}
   170  			defer unix.Unlink(name)
   171  		}
   172  
   173  		// Use a host opener to keep things simple.
   174  		opener := &hostOpener{name: name}
   175  
   176  		pipeOpenState := &pipeOpenState{}
   177  		ctx := contexttest.Context(t)
   178  		pipeOps, err := pipeOpenState.TryOpen(ctx, opener, test.flags)
   179  		if unwrapError(err) != test.err {
   180  			t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
   181  			if pipeOps != nil {
   182  				// Cleanup the state of the pipe, and remove the fd from the
   183  				// fdnotifier.  Sadly this needed to maintain the correctness
   184  				// of other tests because the fdnotifier is global.
   185  				pipeOps.Release(ctx)
   186  			}
   187  			continue
   188  		}
   189  		if (pipeOps != nil) != test.expectFile {
   190  			t.Errorf("%s: got non-nil file %v, want %v", test.desc, pipeOps != nil, test.expectFile)
   191  		}
   192  		if pipeOps != nil {
   193  			// Same as above.
   194  			pipeOps.Release(ctx)
   195  		}
   196  	}
   197  }
   198  
   199  func TestPipeOpenUnblocksEventually(t *testing.T) {
   200  	for _, test := range []struct {
   201  		// desc is the test's description.
   202  		desc string
   203  
   204  		// partnerIsReader is true if the goroutine opening the same pipe as the test case
   205  		// should open the pipe read only.  Otherwise write only.  This also means that the
   206  		// test case will open the pipe in the opposite way.
   207  		partnerIsReader bool
   208  
   209  		// partnerIsBlocking is true if the goroutine opening the same pipe as the test case
   210  		// should do so without the O_NONBLOCK flag, otherwise opens the pipe with O_NONBLOCK
   211  		// until ENXIO is not returned.
   212  		partnerIsBlocking bool
   213  	}{
   214  		{
   215  			desc:              "Blocking Read with blocking writer partner opens eventually",
   216  			partnerIsReader:   false,
   217  			partnerIsBlocking: true,
   218  		},
   219  		{
   220  			desc:              "Blocking Write with blocking reader partner opens eventually",
   221  			partnerIsReader:   true,
   222  			partnerIsBlocking: true,
   223  		},
   224  		{
   225  			desc:              "Blocking Read with non-blocking writer partner opens eventually",
   226  			partnerIsReader:   false,
   227  			partnerIsBlocking: false,
   228  		},
   229  		{
   230  			desc:              "Blocking Write with non-blocking reader partner opens eventually",
   231  			partnerIsReader:   true,
   232  			partnerIsBlocking: false,
   233  		},
   234  	} {
   235  		// Create the pipe.  We do this per-test case to keep tests independent.
   236  		name := pipename()
   237  		if err := mkpipe(name); err != nil {
   238  			t.Errorf("%s: failed to make host pipe: %v", test.desc, err)
   239  			continue
   240  		}
   241  		defer unix.Unlink(name)
   242  
   243  		// Spawn the partner.
   244  		type fderr struct {
   245  			fd  int
   246  			err error
   247  		}
   248  		errch := make(chan fderr, 1)
   249  		go func() {
   250  			var flags int
   251  			if test.partnerIsReader {
   252  				flags = unix.O_RDONLY
   253  			} else {
   254  				flags = unix.O_WRONLY
   255  			}
   256  			if test.partnerIsBlocking {
   257  				fd, err := unix.Open(name, flags, 0666)
   258  				errch <- fderr{fd: fd, err: err}
   259  			} else {
   260  				var fd int
   261  				err := error(unix.ENXIO)
   262  				for err == unix.ENXIO {
   263  					fd, err = unix.Open(name, flags|unix.O_NONBLOCK, 0666)
   264  					time.Sleep(1 * time.Second)
   265  				}
   266  				errch <- fderr{fd: fd, err: err}
   267  			}
   268  		}()
   269  
   270  		// Setup file flags for either a read only or write only open.
   271  		flags := fs.FileFlags{
   272  			Read:  !test.partnerIsReader,
   273  			Write: test.partnerIsReader,
   274  		}
   275  
   276  		// Open the pipe in a blocking way, which should succeed eventually.
   277  		opener := &hostOpener{name: name}
   278  		ctx := contexttest.Context(t)
   279  		pipeOps, err := Open(ctx, opener, flags)
   280  		if pipeOps != nil {
   281  			// Same as TestTryOpen.
   282  			pipeOps.Release(ctx)
   283  		}
   284  
   285  		// Check that the partner opened the file successfully.
   286  		e := <-errch
   287  		if e.err != nil {
   288  			t.Errorf("%s: partner got error %v, wanted nil", test.desc, e.err)
   289  			continue
   290  		}
   291  		// If so, then close the partner fd to avoid leaking an fd.
   292  		unix.Close(e.fd)
   293  
   294  		// Check that our blocking open was successful.
   295  		if err != nil {
   296  			t.Errorf("%s: blocking open got error %v, wanted nil", test.desc, err)
   297  			continue
   298  		}
   299  		if pipeOps == nil {
   300  			t.Errorf("%s: blocking open got nil file, wanted non-nil", test.desc)
   301  			continue
   302  		}
   303  	}
   304  }
   305  
   306  func TestCopiedReadAheadBuffer(t *testing.T) {
   307  	// Create the pipe.
   308  	name := pipename()
   309  	if err := mkpipe(name); err != nil {
   310  		t.Fatalf("failed to make host pipe: %v", err)
   311  	}
   312  	defer unix.Unlink(name)
   313  
   314  	// We're taking advantage of the fact that pipes opened read only always return
   315  	// success, but internally they are not deemed "opened" until we're sure that
   316  	// another writer comes along.  This means we can open the same pipe write only
   317  	// with no problems + write to it, given that opener.Open already tried to open
   318  	// the pipe RDONLY and succeeded, which we know happened if TryOpen returns
   319  	// syserror.ErrwouldBlock.
   320  	//
   321  	// This simulates the open(RDONLY) <-> open(WRONLY)+write race we care about, but
   322  	// does not cause our test to be racy (which would be terrible).
   323  	opener := &hostOpener{name: name}
   324  	pipeOpenState := &pipeOpenState{}
   325  	ctx := contexttest.Context(t)
   326  	pipeOps, err := pipeOpenState.TryOpen(ctx, opener, fs.FileFlags{Read: true})
   327  	if pipeOps != nil {
   328  		pipeOps.Release(ctx)
   329  		t.Fatalf("open(%s, %o) got file, want nil", name, unix.O_RDONLY)
   330  	}
   331  	if err != syserror.ErrWouldBlock {
   332  		t.Fatalf("open(%s, %o) got error %v, want %v", name, unix.O_RDONLY, err, syserror.ErrWouldBlock)
   333  	}
   334  
   335  	// Then open the same pipe write only and write some bytes to it.  The next
   336  	// time we try to open the pipe read only again via the pipeOpenState, we should
   337  	// succeed and buffer some of the bytes written.
   338  	fd, err := unix.Open(name, unix.O_WRONLY, 0666)
   339  	if err != nil {
   340  		t.Fatalf("open(%s, %o) got error %v, want nil", name, unix.O_WRONLY, err)
   341  	}
   342  	defer unix.Close(fd)
   343  
   344  	data := []byte("hello")
   345  	if n, err := unix.Write(fd, data); n != len(data) || err != nil {
   346  		t.Fatalf("write(%v) got (%d, %v), want (%d, nil)", data, n, err, len(data))
   347  	}
   348  
   349  	// Try the read again, knowing that it should succeed this time.
   350  	pipeOps, err = pipeOpenState.TryOpen(ctx, opener, fs.FileFlags{Read: true})
   351  	if pipeOps == nil {
   352  		t.Fatalf("open(%s, %o) got nil file, want not nil", name, unix.O_RDONLY)
   353  	}
   354  	defer pipeOps.Release(ctx)
   355  
   356  	if err != nil {
   357  		t.Fatalf("open(%s, %o) got error %v, want nil", name, unix.O_RDONLY, err)
   358  	}
   359  
   360  	inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
   361  		Type: fs.Pipe,
   362  	})
   363  	file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, pipeOps)
   364  
   365  	// Check that the file we opened points to a pipe with a non-empty read ahead buffer.
   366  	bufsize := len(pipeOps.readAheadBuffer)
   367  	if bufsize != 1 {
   368  		t.Fatalf("read ahead buffer got %d bytes, want %d", bufsize, 1)
   369  	}
   370  
   371  	// Now for the final test, try to read everything in, expecting to get back all of
   372  	// the bytes that were written at once.  Note that in the wild there is no atomic
   373  	// read size so expecting to get all bytes from a single writer when there are
   374  	// multiple readers is a bad expectation.
   375  	buf := make([]byte, len(data))
   376  	ioseq := usermem.BytesIOSequence(buf)
   377  	n, err := pipeOps.Read(ctx, file, ioseq, 0)
   378  	if err != nil {
   379  		t.Fatalf("read request got error %v, want nil", err)
   380  	}
   381  	if n != int64(len(data)) {
   382  		t.Fatalf("read request got %d bytes, want %d", n, len(data))
   383  	}
   384  	if !bytes.Equal(buf, data) {
   385  		t.Errorf("read request got bytes [%v], want [%v]", buf, data)
   386  	}
   387  }
   388  
   389  func TestPipeHangup(t *testing.T) {
   390  	for _, test := range []struct {
   391  		// desc is the test's description.
   392  		desc string
   393  
   394  		// flags control how we open our end of the pipe and must be read
   395  		// only or write only.  They also dicate how a coordinating partner
   396  		// fd is opened, which is their inverse (read only -> write only, etc).
   397  		flags fs.FileFlags
   398  
   399  		// hangupSelf if true causes the test case to close our end of the pipe
   400  		// and causes hangup errors to be asserted on our coordinating partner's
   401  		// fd.  If hangupSelf is false, then our partner's fd is closed and the
   402  		// hangup errors are expected on our end of the pipe.
   403  		hangupSelf bool
   404  	}{
   405  		{
   406  			desc:  "Read only gets hangup error",
   407  			flags: fs.FileFlags{Read: true},
   408  		},
   409  		{
   410  			desc:  "Write only gets hangup error",
   411  			flags: fs.FileFlags{Write: true},
   412  		},
   413  		{
   414  			desc:       "Read only generates hangup error",
   415  			flags:      fs.FileFlags{Read: true},
   416  			hangupSelf: true,
   417  		},
   418  		{
   419  			desc:       "Write only generates hangup error",
   420  			flags:      fs.FileFlags{Write: true},
   421  			hangupSelf: true,
   422  		},
   423  	} {
   424  		if test.flags.Read == test.flags.Write {
   425  			t.Errorf("%s: test requires a single reader or writer", test.desc)
   426  			continue
   427  		}
   428  
   429  		// Create the pipe.  We do this per-test case to keep tests independent.
   430  		name := pipename()
   431  		if err := mkpipe(name); err != nil {
   432  			t.Errorf("%s: failed to make host pipe: %v", test.desc, err)
   433  			continue
   434  		}
   435  		defer unix.Unlink(name)
   436  
   437  		// Fire off a partner routine which tries to open the same pipe blocking,
   438  		// which will synchronize with us.  The channel allows us to get back the
   439  		// fd once we expect this partner routine to succeed, so we can manifest
   440  		// hangup events more directly.
   441  		fdchan := make(chan int, 1)
   442  		go func() {
   443  			// Be explicit about the flags to protect the test from
   444  			// misconfiguration.
   445  			var flags int
   446  			if test.flags.Read {
   447  				flags = unix.O_WRONLY
   448  			} else {
   449  				flags = unix.O_RDONLY
   450  			}
   451  			fd, err := unix.Open(name, flags, 0666)
   452  			if err != nil {
   453  				t.Logf("Open(%q, %o, 0666) partner failed: %v", name, flags, err)
   454  			}
   455  			fdchan <- fd
   456  		}()
   457  
   458  		// Open our end in a blocking way to ensure that we coordinate.
   459  		opener := &hostOpener{name: name}
   460  		ctx := contexttest.Context(t)
   461  		pipeOps, err := Open(ctx, opener, test.flags)
   462  		if err != nil {
   463  			t.Errorf("%s: Open got error %v, want nil", test.desc, err)
   464  			continue
   465  		}
   466  		// Don't defer file.DecRef here because that causes the hangup we're
   467  		// trying to test for.
   468  
   469  		// Expect the partner routine to have coordinated with us and get back
   470  		// its open fd.
   471  		f := <-fdchan
   472  		if f < 0 {
   473  			t.Errorf("%s: partner routine got fd %d, want > 0", test.desc, f)
   474  			pipeOps.Release(ctx)
   475  			continue
   476  		}
   477  
   478  		if test.hangupSelf {
   479  			// Hangup self and assert that our partner got the expected hangup
   480  			// error.
   481  			pipeOps.Release(ctx)
   482  
   483  			if test.flags.Read {
   484  				// Partner is writer.
   485  				assertWriterHungup(t, test.desc, fd.NewReadWriter(f))
   486  			} else {
   487  				// Partner is reader.
   488  				assertReaderHungup(t, test.desc, fd.NewReadWriter(f))
   489  			}
   490  		} else {
   491  			// Hangup our partner and expect us to get the hangup error.
   492  			unix.Close(f)
   493  			defer pipeOps.Release(ctx)
   494  
   495  			if test.flags.Read {
   496  				assertReaderHungup(t, test.desc, pipeOps.(*pipeOperations).file)
   497  			} else {
   498  				assertWriterHungup(t, test.desc, pipeOps.(*pipeOperations).file)
   499  			}
   500  		}
   501  	}
   502  }
   503  
   504  func assertReaderHungup(t *testing.T, desc string, reader io.Reader) bool {
   505  	// Drain the pipe completely, it might have crap in it, but expect EOF eventually.
   506  	var err error
   507  	for err == nil {
   508  		_, err = reader.Read(make([]byte, 10))
   509  	}
   510  	if err != io.EOF {
   511  		t.Errorf("%s: read from self after hangup got error %v, want %v", desc, err, io.EOF)
   512  		return false
   513  	}
   514  	return true
   515  }
   516  
   517  func assertWriterHungup(t *testing.T, desc string, writer io.Writer) bool {
   518  	if _, err := writer.Write([]byte("hello")); !linuxerr.Equals(linuxerr.EPIPE, unwrapError(err)) {
   519  		t.Errorf("%s: write to self after hangup got error %v, want %v", desc, err, linuxerr.EPIPE)
   520  		return false
   521  	}
   522  	return true
   523  }