1 /*
2  * Copyright (c) 2011 The Native Client Authors. All rights reserved.
3  * Use of this source code is governed by a BSD-style license that can be
4  * found in the LICENSE file.
5  */
6 
7 /*
8  * NaCl Service Runtime.  Directory descriptor / Handle abstraction.
9  *
10  * Note that we avoid using the thread-specific data / thread local
11  * storage access to the "errno" variable, and instead use the raw
12  * system call return interface of small negative numbers as errors.
13  */
14 
15 #include <dirent.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <limits.h>
19 #include <linux/types.h>
20 #include <linux/unistd.h>
21 #include <stddef.h>
22 #include <stdint.h>
23 #include <string.h>
24 #include <sys/mman.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28 
29 #include "native_client/src/shared/platform/nacl_check.h"
30 #include "native_client/src/shared/platform/nacl_host_desc.h"
31 #include "native_client/src/shared/platform/nacl_host_dir.h"
32 #include "native_client/src/shared/platform/nacl_log.h"
33 #include "native_client/src/shared/platform/nacl_sync.h"
34 #include "native_client/src/shared/platform/nacl_sync_checked.h"
35 
36 #include "native_client/src/trusted/service_runtime/nacl_config.h"
37 
38 #include "native_client/src/trusted/service_runtime/include/bits/mman.h"
39 #include "native_client/src/trusted/service_runtime/include/sys/dirent.h"
40 #include "native_client/src/trusted/service_runtime/include/sys/errno.h"
41 #include "native_client/src/trusted/service_runtime/include/sys/fcntl.h"
42 #include "native_client/src/trusted/service_runtime/include/sys/stat.h"
43 
44 #ifdef _syscall3
45 _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count)
46 
47 int getdents(unsigned int fd, struct dirent* dirp, unsigned int count);
48 #else
49 # include <sys/syscall.h>
getdents(unsigned int fd,struct dirent * dirp,unsigned int count)50 int getdents(unsigned int fd, struct dirent* dirp, unsigned int count) {
51   return syscall(__NR_getdents, fd, dirp, count);
52 }
53 #endif
54 
55 struct linux_dirent {  /* offsets, ILP32 and LP64 */
56   unsigned long  d_ino;             /*  0,  0 */
57   unsigned long  d_off;             /*  4,  8 */
58   unsigned short d_reclen;          /*  8, 16 */
59   char           d_name[1];         /* 10, 18 */
60   /* actual length is d_reclen - 2 - offsetof(struct linux_dirent, d_name) */
61   /*
62    * char pad;    / Zero padding byte
63    * char d_type; / File type (only since Linux 2.6.4; offset is d_reclen - 1)
64    */
65 };
66 
67 
68 /*
69  * from native_client/src/trusted/service_runtime/include/sys/dirent.h:
70 
71 struct nacl_abi_dirent {                                 offsets, NaCl
72   nacl_abi_ino_t nacl_abi_d_ino;                                0
73   nacl_abi_off_t nacl_abi_d_off;                                8
74   uint16_t       nacl_abi_d_reclen;                            16
75   char           nacl_abi_d_name[NACL_ABI_MAXNAMLEN + 1];      18
76 };
77 
78  * It would be nice if we didn't have to buffer dirent data.  Is it
79  * possible, when the untrusted NaCl module invokes getdent, to
80  * determine a (different) buffer size value to use with the host OS
81  * (Linux)?
82  *
83  * Since the actual length is d_reclen, do we know if d_reclen is
84  * padded out to a multiple of the alignment restriction for longs?
85  * Best to assume that either may hold. On x86 where unaligned
86  * accesses are okay, this might be fine; on other architectures where
87  * it isn't, then presumably d_reclen will include the padding, and
88  * d_type (which we don't use) is on the far end of the padding.
89  *
90  * Given a user-supplied buffer of N bytes to hold nacl_abi_dirent
91  * entries, what is the size of the buffer that should be supplied to
92  * the Linux kernel so that we will never end up with more information
93  * than we can copy / return back to the user?
94  *
95  * For LP64, the answer is relatively simple: everything is the same
96  * size, except that the kernel is going to use one more byte for the
97  * d_type entry.  Since copying to the nacl_abi_dirent omits that, the
98  * transfer shrinks the space needed.
99  *
100  * For ILP32, the answer is a little harder.  The linux_dirent use 8
101  * fewer bytes for the entries before d_name, but one more at the end
102  * for d_type.  So, when does the worst case expansion occur?  The
103  * number of dirent entries is multiplied by the expansion, so we need
104  * to determine the smallest dirent entry.  Assuming single character
105  * file names, a user's buffer of size N can hold int(N/20) dirents.
106  * The linux_dirent, with no alignment pads, but with d_type (we
107  * assume newer kernels only), will take 13 bytes per entry, so we had
108  * better not claim to have more than 13*int(N/20) bytes of space
109  * available to the Linux kernel.  (We don't need to check in our
110  * platform qualification check since "older" kernels are pre 2.6.4,
111  * and all reasonable Linux distributions use newer kernels.)
112  *
113  * Suppose the user gave us a buffer of 40 bytes.  This is enough for
114  * two dirent structures containing information about files each with
115  * a single character name.  So, we invoke Linux's getdents with a 26
116  * byte buffer.  What happens if the next actual directory entry's
117  * name is 40-18-1=21 bytes long?  It would have fit in the user's
118  * buffer, but the linux_dirent buffer required for that entry is
119  * 10+21+2 bytes in length, or 33 bytes.  We supplied linux with a 26
120  * byte buffer, so it should respond with EINVAL since the buffer
121  * space is too small.
122  *
123  * This argues against a simple scheme that avoids buffering.
124  *
125  * We could, when we encounter EINVAL, increase the buffer size used
126  * with the host OS.  How do we expand and could that might result in
127  * too much being read in?
128  */
129 
NaClHostDirCtor(struct NaClHostDir * d,int dir_desc)130 int NaClHostDirCtor(struct NaClHostDir  *d,
131                     int                 dir_desc) {
132   if (!NaClMutexCtor(&d->mu)) {
133     return -NACL_ABI_ENOMEM;
134   }
135   d->fd = dir_desc;
136   d->cur_byte = 0;
137   d->nbytes = 0;
138   NaClLog(3, "NaClHostDirCtor: success.\n");
139   return 0;
140 }
141 
NaClHostDirOpen(struct NaClHostDir * d,char * path)142 int NaClHostDirOpen(struct NaClHostDir  *d,
143                     char                *path) {
144   int         fd;
145   struct stat stbuf;
146   int         rv;
147 
148   NaClLog(3, "NaClHostDirOpen(0x%08"NACL_PRIxPTR", %s)\n", (uintptr_t) d, path);
149   if (NULL == d) {
150     NaClLog(LOG_FATAL, "NaClHostDirOpen: 'this' is NULL\n");
151   }
152 
153   NaClLog(3, "NaClHostDirOpen: invoking open(%s)\n", path);
154   fd = open(path, O_RDONLY);
155   NaClLog(3, "NaClHostDirOpen: got DIR* %d\n", fd);
156   if (-1 == fd) {
157     NaClLog(LOG_ERROR,
158             "NaClHostDirOpen: open returned -1, errno %d\n", errno);
159     return -NaClXlateErrno(errno);
160   }
161   /* check that it is really a directory */
162   if (-1 == fstat(fd, &stbuf)) {
163     NaClLog(LOG_ERROR,
164             "NaClHostDirOpen: fstat failed?!?  errno %d\n", errno);
165     (void) close(fd);
166     return -NaClXlateErrno(errno);
167   }
168   if (!S_ISDIR(stbuf.st_mode)) {
169     (void) close(fd);
170     return -NACL_ABI_ENOTDIR;
171   }
172   rv = NaClHostDirCtor(d, fd);
173   return rv;
174 }
175 
176 /*
177  * Copy and translate a single linux_dirent to nacl_abi_dirent.
178  * Returns number of bytes consumed (includes alignment adjustment for
179  * next entry).
180  *
181  * TODO(bsy): add filesystem info argument to specify which
182  * directories are "root" inodes, to rewrite the inode number of '..'
183  * as appropriate.
184  */
NaClCopyDirent(struct NaClHostDir * d,void * buf,size_t len)185 static ssize_t NaClCopyDirent(struct NaClHostDir *d,
186                               void               *buf,
187                               size_t             len) {
188   struct linux_dirent             *ldp = (struct linux_dirent *) (
189       d->dirent_buf
190       + d->cur_byte);
191   struct nacl_abi_dirent volatile *nadp;
192   size_t                          adjusted_size;
193 
194   /* make sure the buffer is aligned */
195   CHECK(0 == ((sizeof(nacl_abi_ino_t) - 1) & (uintptr_t) buf));
196 
197   if (d->cur_byte == d->nbytes) {
198     return 0;  /* none available */
199   }
200   CHECK(d->cur_byte < d->nbytes);
201   CHECK(ldp->d_reclen <= d->nbytes - d->cur_byte);
202   /* no partial record transferred. */
203 
204   nadp = (struct nacl_abi_dirent volatile *) buf;
205 
206   /*
207    * is there enough space? assume Linux is sane, so no ssize_t
208    * overflow in the adjusted_size computation.  (NAME_MAX is small.)
209    */
210   CHECK(NAME_MAX < 256);
211   adjusted_size = offsetof(struct nacl_abi_dirent, nacl_abi_d_name)
212       + strlen(ldp->d_name) + 1;  /* NUL termination */
213   /* pad for alignment for access to d_ino */
214   adjusted_size = (adjusted_size + (sizeof(nacl_abi_ino_t) - 1))
215       & ~(sizeof(nacl_abi_ino_t) - 1);
216   if (len < adjusted_size) {
217     return -NACL_ABI_EINVAL;  /* result buffer is too small */
218   }
219 
220   nadp->nacl_abi_d_ino = ldp->d_ino;
221   nadp->nacl_abi_d_off = ldp->d_off;
222   nadp->nacl_abi_d_reclen = adjusted_size;
223   NaClLog(4, "NaClCopyDirent: %s\n", ldp->d_name);
224   strcpy((char *) nadp->nacl_abi_d_name, ldp->d_name);
225   /* NB: some padding bytes may not get overwritten */
226 
227   d->cur_byte += ldp->d_reclen;
228 
229   NaClLog(4, "NaClCopyDirent: returning %"NACL_PRIuS"\n", adjusted_size);
230   return (ssize_t) adjusted_size;
231 }
232 
NaClStreamDirents(struct NaClHostDir * d,void * buf,size_t len)233 static ssize_t NaClStreamDirents(struct NaClHostDir *d,
234                                  void               *buf,
235                                  size_t             len) {
236   ssize_t retval;
237   size_t  xferred = 0;
238   ssize_t entry_size;
239 
240   NaClXMutexLock(&d->mu);
241   while (len > 0) {
242     NaClLog(4, "NaClStreamDirents: loop, xferred = %"NACL_PRIuS"\n", xferred);
243     entry_size = NaClCopyDirent(d, buf, len);
244     if (0 == entry_size) {
245       CHECK(d->cur_byte == d->nbytes);
246       retval = getdents(d->fd,
247                         (struct dirent *) d->dirent_buf,
248                         sizeof d->dirent_buf);
249       if (-1 == retval) {
250         if (xferred > 0) {
251           /* next time through, we'll pick up the error again */
252           goto cleanup;
253         } else {
254           xferred = -NaClXlateErrno(errno);
255           goto cleanup;
256         }
257       } else if (0 == retval) {
258         goto cleanup;
259       }
260       d->cur_byte = 0;
261       d->nbytes = retval;
262     } else if (entry_size < 0) {
263       /*
264        * The only error return from NaClCopyDirent is NACL_ABI_EINVAL
265        * due to destinaton buffer too small for the current entry.  If
266        * we had copied some entries before, we were successful;
267        * otherwise report that the buffer is too small for the next
268        * directory entry.
269        */
270       if (xferred > 0) {
271         goto cleanup;
272       } else {
273         xferred = entry_size;
274         goto cleanup;
275       }
276     }
277     /* entry_size > 0, maybe copy another */
278     buf = (void *) ((char *) buf + entry_size);
279     CHECK(len >= (size_t) entry_size);
280     len -= entry_size;
281     xferred += entry_size;
282   }
283   /* perfect fit! */
284  cleanup:
285   NaClXMutexUnlock(&d->mu);
286   return xferred;
287 }
288 
NaClHostDirGetdents(struct NaClHostDir * d,void * buf,size_t len)289 ssize_t NaClHostDirGetdents(struct NaClHostDir  *d,
290                             void                *buf,
291                             size_t              len) {
292   int retval;
293 
294   if (NULL == d) {
295     NaClLog(LOG_FATAL, "NaClHostDirGetdents: 'this' is NULL\n");
296   }
297   NaClLog(3, "NaClHostDirGetdents(0x%08"NACL_PRIxPTR", %"NACL_PRIuS"):\n",
298           (uintptr_t) buf, len);
299 
300   if (0 != ((__alignof__(struct nacl_abi_dirent) - 1) & (uintptr_t) buf)) {
301     retval = -NACL_ABI_EINVAL;
302     goto cleanup;
303   }
304 
305   retval = NaClStreamDirents(d, buf, len);
306  cleanup:
307   NaClLog(3, "NaClHostDirGetdents: returned %d\n", retval);
308   return retval;
309 }
310 
NaClHostDirRewind(struct NaClHostDir * d)311 int NaClHostDirRewind(struct NaClHostDir *d) {
312   if (NULL == d) {
313     NaClLog(LOG_FATAL, "NaClHostDirRewind: 'this' is NULL\n");
314   }
315   return -NaClXlateErrno(lseek64(d->fd, 0, SEEK_SET));
316 }
317 
NaClHostDirClose(struct NaClHostDir * d)318 int NaClHostDirClose(struct NaClHostDir *d) {
319   int retval;
320 
321   if (NULL == d) {
322     NaClLog(LOG_FATAL, "NaClHostDirClose: 'this' is NULL\n");
323   }
324   NaClLog(3, "NaClHostDirClose(%d)\n", d->fd);
325   retval = close(d->fd);
326   d->fd = -1;
327   NaClMutexDtor(&d->mu);
328   return (-1 == retval) ? -NaClXlateErrno(errno) : retval;
329 }
330 
NaClHostDirFchdir(struct NaClHostDir * d)331 int NaClHostDirFchdir(struct NaClHostDir *d) {
332   if (-1 == fchdir(d->fd)) {
333     return -NaClXlateErrno(errno);
334   }
335   return 0;
336 }
337 
NaClHostDirFchmod(struct NaClHostDir * d,int mode)338 int NaClHostDirFchmod(struct NaClHostDir *d, int mode) {
339   if (-1 == fchmod(d->fd, mode)) {
340     return -NaClXlateErrno(errno);
341   }
342   return 0;
343 }
344 
NaClHostDirFsync(struct NaClHostDir * d)345 int NaClHostDirFsync(struct NaClHostDir *d) {
346   if (-1 == fsync(d->fd)) {
347     return -NaClXlateErrno(errno);
348   }
349   return 0;
350 }
351 
NaClHostDirFdatasync(struct NaClHostDir * d)352 int NaClHostDirFdatasync(struct NaClHostDir *d) {
353   if (-1 == fdatasync(d->fd)) {
354     return -NaClXlateErrno(errno);
355   }
356   return 0;
357 }
358