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 }