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