gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/syscalls/linux/sys_getdents.go (about)

     1  // Copyright 2020 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 linux
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    21  	"gvisor.dev/gvisor/pkg/hostarch"
    22  	"gvisor.dev/gvisor/pkg/sentry/arch"
    23  	"gvisor.dev/gvisor/pkg/sentry/kernel"
    24  	"gvisor.dev/gvisor/pkg/sentry/vfs"
    25  	"gvisor.dev/gvisor/pkg/sync"
    26  	"gvisor.dev/gvisor/pkg/usermem"
    27  )
    28  
    29  // Getdents implements Linux syscall getdents(2).
    30  func Getdents(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
    31  	return getdents(t, args, false /* isGetdents64 */)
    32  }
    33  
    34  // Getdents64 implements Linux syscall getdents64(2).
    35  func Getdents64(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
    36  	return getdents(t, args, true /* isGetdents64 */)
    37  }
    38  
    39  // DirentStructBytesWithoutName is enough to fit (struct linux_dirent) and
    40  // (struct linux_dirent64) without accounting for the name parameter.
    41  const DirentStructBytesWithoutName = 8 + 8 + 2 + 1 + 1
    42  
    43  func getdents(t *kernel.Task, args arch.SyscallArguments, isGetdents64 bool) (uintptr, *kernel.SyscallControl, error) {
    44  	fd := args[0].Int()
    45  	addr := args[1].Pointer()
    46  	size := args[2].Int()
    47  
    48  	if size < DirentStructBytesWithoutName {
    49  		return 0, nil, linuxerr.EINVAL
    50  	}
    51  
    52  	file := t.GetFile(fd)
    53  	if file == nil {
    54  		return 0, nil, linuxerr.EBADF
    55  	}
    56  	defer file.DecRef(t)
    57  
    58  	// We want to be sure of the allowed buffer size before calling IterDirents,
    59  	// because this function depends on IterDirents saving state of which dirent
    60  	// was the last one that was successfully operated on.
    61  	allowedSize, err := t.MemoryManager().EnsurePMAsExist(t, addr, int64(size), usermem.IOOpts{
    62  		AddressSpaceActive: true,
    63  	})
    64  	if allowedSize == 0 {
    65  		return 0, nil, err
    66  	}
    67  
    68  	cb := getGetdentsCallback(t, int(allowedSize), int(size), isGetdents64)
    69  	err = file.IterDirents(t, cb)
    70  	n, _ := t.CopyOutBytes(addr, cb.buf[:cb.copied])
    71  
    72  	putGetdentsCallback(cb)
    73  
    74  	// Only report an error in case we didn't copy anything.
    75  	// If we did manage to give _something_ to the caller then the correct
    76  	// behaviour is to return success.
    77  	if n == 0 {
    78  		return 0, nil, err
    79  	}
    80  
    81  	return uintptr(n), nil, nil
    82  }
    83  
    84  type getdentsCallback struct {
    85  	t                *kernel.Task
    86  	buf              []byte
    87  	copied           int
    88  	userReportedSize int
    89  	isGetdents64     bool
    90  }
    91  
    92  var getdentsCallbackPool = sync.Pool{
    93  	New: func() any {
    94  		return &getdentsCallback{}
    95  	},
    96  }
    97  
    98  func getGetdentsCallback(t *kernel.Task, size int, userReportedSize int, isGetdents64 bool) *getdentsCallback {
    99  	cb := getdentsCallbackPool.Get().(*getdentsCallback)
   100  	buf := cb.buf
   101  	if cap(buf) < size {
   102  		buf = make([]byte, size)
   103  	} else {
   104  		buf = buf[:size]
   105  	}
   106  
   107  	*cb = getdentsCallback{
   108  		t:                t,
   109  		buf:              buf,
   110  		copied:           0,
   111  		userReportedSize: userReportedSize,
   112  		isGetdents64:     isGetdents64,
   113  	}
   114  	return cb
   115  }
   116  
   117  func putGetdentsCallback(cb *getdentsCallback) {
   118  	cb.t = nil
   119  	cb.buf = cb.buf[:0]
   120  	getdentsCallbackPool.Put(cb)
   121  }
   122  
   123  // Handle implements vfs.IterDirentsCallback.Handle.
   124  func (cb *getdentsCallback) Handle(dirent vfs.Dirent) error {
   125  	remaining := len(cb.buf) - cb.copied
   126  	if cb.isGetdents64 {
   127  		// struct linux_dirent64 {
   128  		//     ino64_t        d_ino;    /* 64-bit inode number */
   129  		//     off64_t        d_off;    /* 64-bit offset to next structure */
   130  		//     unsigned short d_reclen; /* Size of this dirent */
   131  		//     unsigned char  d_type;   /* File type */
   132  		//     char           d_name[]; /* Filename (null-terminated) */
   133  		// };
   134  		size := DirentStructBytesWithoutName + len(dirent.Name)
   135  		size = (size + 7) &^ 7 // round up to multiple of 8
   136  		if size > remaining {
   137  			// This is only needed to imitate Linux, since it writes out to the user
   138  			// as it's iterating over dirs. We don't do that because we can't take
   139  			// the mm.mappingMu while holding the filesystem mutex.
   140  			if cb.copied == 0 && cb.userReportedSize >= size {
   141  				return linuxerr.EFAULT
   142  			}
   143  			return linuxerr.EINVAL
   144  		}
   145  		buf := cb.buf[cb.copied : cb.copied+size]
   146  		hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino)
   147  		hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff))
   148  		hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size))
   149  		buf[18] = dirent.Type
   150  		copy(buf[19:], dirent.Name)
   151  		// Zero out all remaining bytes in buf, including the NUL terminator
   152  		// after dirent.Name.
   153  		bufTail := buf[19+len(dirent.Name):]
   154  		clear(bufTail)
   155  		cb.copied += size
   156  	} else {
   157  		// struct linux_dirent {
   158  		//     unsigned long  d_ino;     /* Inode number */
   159  		//     unsigned long  d_off;     /* Offset to next linux_dirent */
   160  		//     unsigned short d_reclen;  /* Length of this linux_dirent */
   161  		//     char           d_name[];  /* Filename (null-terminated) */
   162  		//                       /* length is actually (d_reclen - 2 -
   163  		//                          offsetof(struct linux_dirent, d_name)) */
   164  		//     /*
   165  		//     char           pad;       // Zero padding byte
   166  		//     char           d_type;    // File type (only since Linux
   167  		//                               // 2.6.4); offset is (d_reclen - 1)
   168  		//     */
   169  		// };
   170  		if cb.t.Arch().Width() != 8 {
   171  			panic(fmt.Sprintf("unsupported sizeof(unsigned long): %d", cb.t.Arch().Width()))
   172  		}
   173  		size := DirentStructBytesWithoutName + len(dirent.Name)
   174  		size = (size + 7) &^ 7 // round up to multiple of sizeof(long)
   175  		if size > remaining {
   176  			if cb.copied == 0 && cb.userReportedSize >= size {
   177  				return linuxerr.EFAULT
   178  			}
   179  			return linuxerr.EINVAL
   180  		}
   181  		buf := cb.buf[cb.copied : cb.copied+size]
   182  		hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino)
   183  		hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff))
   184  		hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size))
   185  		copy(buf[18:], dirent.Name)
   186  		// Zero out all remaining bytes in buf, including the NUL terminator
   187  		// after dirent.Name and the zero padding byte between the name and
   188  		// dirent type.
   189  		bufTail := buf[18+len(dirent.Name) : size-1]
   190  		clear(bufTail)
   191  		buf[size-1] = dirent.Type
   192  		cb.copied += size
   193  	}
   194  
   195  	return nil
   196  }