source: trunk/athena/lib/locker/mountpoint.c @ 13522

Revision 13522, 15.3 KB checked in by ghudson, 25 years ago (diff)
In locker__canonicalize_path(): Don't double-free *pathp (pointed out by mycroft). Don't free *buildfromp at all; it's not necessary. Document the interface better.
Line 
1/* Copyright 1998 by the Massachusetts Institute of Technology.
2 *
3 * Permission to use, copy, modify, and distribute this
4 * software and its documentation for any purpose and without
5 * fee is hereby granted, provided that the above copyright
6 * notice appear in all copies and that both that copyright
7 * notice and this permission notice appear in supporting
8 * documentation, and that the name of M.I.T. not be used in
9 * advertising or publicity pertaining to distribution of the
10 * software without specific, written prior permission.
11 * M.I.T. makes no representations about the suitability of
12 * this software for any purpose.  It is provided "as is"
13 * without express or implied warranty.
14 */
15
16/* This file is part of liblocker. It deals with canonicalizing and
17 * creating mountpoints, and the associated security issues.
18 */
19
20static const char rcsid[] = "$Id: mountpoint.c,v 1.6 1999-08-19 18:38:59 ghudson Exp $";
21
22#include <sys/stat.h>
23#include <errno.h>
24#include <fcntl.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <unistd.h>
29
30#include "locker.h"
31#include "locker_private.h"
32
33/* Include <sys/param.h> for MAXPATHLEN (and MAXSYMLINKS). We assume
34 * that if some future OS doesn't have a maximum path length then
35 * they'll also provide replacements for getcwd() and readlink() that
36 * don't require the caller to guess how large a buffer to provide.
37 */
38#include <sys/param.h>
39
40#define MODE_MASK (S_IRWXU | S_IRWXG | S_IRWXO)
41#define LOCKER_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
42#define BAD_MODE_BITS (S_IWGRP | S_IWOTH)
43
44static int mountpoint_mkdir(locker_context context, locker_attachent *at,
45                            char *path);
46static int mountpoint_rmdir(locker_context context, locker_attachent *at,
47                            char *path);
48
49static int get_dirlock(locker_context context, locker_attachent *at,
50                       int type);
51static void release_dirlock(locker_context context, locker_attachent *at);
52
53/* Canonicalize a path and optionally make sure that it doesn't pass
54 * through any mountpoints or user-writable directories.  *pathp must
55 * contain an allocated string, which may be freed and replaced with
56 * another allocated string containing the canonicalized path.  If
57 * buildfromp is not NULL, *buildfromp is set to an allocated string
58 * containing the first ancestor directory of the canonicalized path
59 * which doesn't exist, or to NULL if the canonicalized path already
60 * exists.
61 */
62int locker__canonicalize_path(locker_context context, int check,
63                              char **pathp, char **buildfromp)
64{
65  char *path, *buildfrom = NULL, *cur, *end, *p;
66  struct stat st;
67  int nlinks = 0, status = 0;
68  dev_t last_dev = 0;
69  uid_t uid = geteuid();
70
71  path = *pathp;
72
73  /* If it doesn't start with '/', prepend cwd. */
74  if (path[0] != '/')
75    {
76      char cwdbuf[MAXPATHLEN], *newpath;
77
78      if (!getcwd(cwdbuf, sizeof(cwdbuf)))
79        {
80          locker__error(context, "Could not retrieve current working "
81                        "directory:\n%s.\n", strerror(errno));
82          free(path);
83          return LOCKER_EBADPATH;
84        }
85      if (!strcmp(cwdbuf, "/"))
86        *cwdbuf = '\0';         /* Avoid generating "//foo". */
87
88      newpath = malloc(strlen(cwdbuf) + strlen(path) + 2);
89      if (!newpath)
90        {
91          free(path);
92          locker__error(context, "Out of memory canonicalizing mountpoint.\n");
93          return LOCKER_ENOMEM;
94        }
95
96      sprintf(newpath, "%s/%s", cwdbuf, path);
97      free(path);
98      path = newpath;
99
100      /* Don't need to canonicalize cwd part, so skip that. */
101      cur = path + strlen(cwdbuf) + 1;
102    }
103  else
104    cur = path + 1;
105
106  /* Remove trailing slash. */
107  p = strrchr(path, '/');
108  if (p && !*(p + 1) && p > path)
109    *p = '\0';
110
111  if (uid != context->user)
112    seteuid(context->user);
113
114  /* Expand symlinks. */
115  do
116    {
117      end = strchr(cur, '/');
118      if (end)
119        *end = '\0';
120
121      /* cur points to the character after a '/'. Everything before cur is
122       * canonicalized. If cur points to the final component, end is NULL.
123       * Otherwise, end points to a '\0' that is covering the next '/'
124       * after cur.
125       */
126
127      /* Handle special cases. */
128      if (!*cur || !strcmp(cur, "."))
129        {
130          /* "." can't be final path component (since we might not have
131           * wanted to resolve the final path component, and now it's
132           * too late.
133           */
134          if (!end)
135            {
136              locker__error(context, "Path cannot end with \".\" (%s).\n",
137                            path);
138              status = LOCKER_EBADPATH;
139              goto cleanup;
140            }
141
142          /* Copy the next component over this one. */
143          strcpy(cur, end + 1);
144          continue;
145        }
146      else if (!strcmp(cur, ".."))
147        {
148          /* ".." can't be final path component for similar reasons
149           * to above.
150           */
151          if (!end)
152            {
153              locker__error(context, "%s: Path cannot end with \"..\" (%s).\n",
154                            path);
155              status = LOCKER_EBADPATH;
156              goto cleanup;
157            }
158
159          /* Remove this component and the previous one. */
160          cur -= 2;
161          while (cur > path && *cur != '/')
162            cur--;
163
164          if (cur < path)
165            cur++;              /* "/.." == "/" */
166
167          /* Copy the next component over the previous one. */
168          strcpy(cur, end + 1);
169          continue;
170        }
171
172      /* Don't resolve the final component unless we need to check it. */
173      if (!end && (check != LOCKER_CANON_CHECK_ALL))
174        break;
175
176      /* Check if current component is a symlink. */
177      if (lstat(path, &st) < 0)
178        {
179          if (errno == ENOENT)
180            {
181              /* The rest of the path doesn't exist. */
182              buildfrom = strdup(path);
183              if (!buildfrom)
184                {
185                  locker__error(context, "Out of memory canonicalizing "
186                                "mountpoint.\n");
187                  status = LOCKER_ENOMEM;
188                  goto cleanup;
189                }
190              if (end)
191                *end = '/';
192              break;
193            }
194          else
195            {
196              locker__error(context, "Could not canonicalize path:\n"
197                            "%s for %s\n", strerror(errno), path);
198              status = LOCKER_EBADPATH;
199              goto cleanup;
200            }
201        }
202      else if (!end && !S_ISDIR(st.st_mode))
203        {
204          locker__error(context, "Final path component is not a directory "
205                        "in \"%s\".\n", path);
206          status = LOCKER_EBADPATH;
207          goto cleanup;
208        }
209      else if (S_ISLNK(st.st_mode))
210        {
211          char linkbuf[MAXPATHLEN], *newpath;
212          int len;
213
214          if (++nlinks > MAXSYMLINKS)
215            {
216              locker__error(context, "Too many levels of symlinks "
217                            "while canonicalizing path \"%s\".\n",
218                            path);
219              status = LOCKER_EBADPATH;
220              goto cleanup;
221            }
222
223          len = readlink(path, linkbuf, sizeof(linkbuf));
224          if (len == -1)
225            {
226              locker__error(context, "Could not canonicalize mountpoint:\n"
227                            "%s while reading symlink %s\n", strerror(errno),
228                            path);
229              status = LOCKER_EBADPATH;
230              goto cleanup;
231            }
232
233          if (linkbuf[0] == '/')
234            {
235              /* It's absolute, so replace existing path with it. */
236              newpath = malloc(len + strlen(end + 1) + 2);
237              if (!newpath)
238                {
239                  locker__error(context, "Out of memory canonicalizing "
240                                "mountpoint.\n");
241                  status = LOCKER_ENOMEM;
242                  goto cleanup;
243                }
244              sprintf(newpath, "%.*s/%s", len, linkbuf, end + 1);
245              free(path);
246              path = newpath;
247              /* And start over from the top. */
248              cur = path + 1;
249            }
250          else
251            {
252              /* Add this in to existing path. */
253              *cur = '\0';
254              newpath = malloc(strlen(path) + len + strlen(end + 1) + 3);
255              if (!newpath)
256                {
257                  locker__error(context, "Out of memory canonicalizing "
258                                "mountpoint.\n");
259                  status = LOCKER_ENOMEM;
260                  goto cleanup;
261                }
262              sprintf(newpath, "%s%.*s/%s", path, len, linkbuf, end + 1);
263              /* cur is effectively unchanged. */
264              cur = newpath + (cur - path);
265              free(path);
266              path = newpath;
267            }
268        }
269      else
270        {
271          if (check != LOCKER_CANON_CHECK_NONE)
272            {
273              /* Check that we can build in this directory. */
274              if (last_dev && st.st_dev != last_dev)
275                {
276                  locker__error(context, "Cannot attach locker on %s:\n"
277                                "directory %s is not on root filesystem.\n",
278                                *pathp, path);
279                  status = LOCKER_EBADPATH;
280                  goto cleanup;
281                }
282              else if (st.st_uid != 0)
283                {
284                  locker__error(context, "Cannot attach locker on %s:\n"
285                                "directory %s is not owned by root.\n",
286                                *pathp, path);
287                  status = LOCKER_EBADPATH;
288                  goto cleanup;
289                }
290              else if (st.st_mode & BAD_MODE_BITS)
291                {
292                  locker__error(context, "Cannot attach locker on %s:\n"
293                                "directory %s is group/other writable.\n",
294                                *pathp, path);
295                  status = LOCKER_EBADPATH;
296                  goto cleanup;
297                }
298
299              last_dev = st.st_dev;
300            }
301
302          /* Replace *end, update cur. */
303          if (end)
304            {
305              *end = '/';
306              cur = end + 1;
307            }
308        }
309    }
310  while (end);
311
312  if (check != LOCKER_CANON_CHECK_NONE)
313    {
314      int len = strlen(context->attachtab);
315      if (!strncmp(context->attachtab, path, len) &&
316          (path[len] == '\0' || path[len] == '/'))
317        {
318          locker__error(context, "Cannot attach locker on %s:\n"
319                        "directory passes through attachtab directory.\n",
320                        *pathp, path);
321          status = LOCKER_EBADPATH;
322          goto cleanup;
323        }
324    }
325
326  /* Remove trailing slash. */
327  p = strrchr(path, '/');
328  if (p && !*(p + 1) && p > path)
329    *p = '\0';
330
331  *pathp = path;
332  if (buildfromp)
333    *buildfromp = buildfrom;
334  else
335    free(buildfrom);
336
337cleanup:
338  if (uid != context->user)
339    seteuid(uid);
340  if (status)
341    {
342      free(path);
343      free(buildfrom);
344    }
345  return status;
346}
347
348/* Create any directories needed to attach something to the named
349 * mountpoint. Record the fact that we created the directory so that
350 * we know to delete it later.
351 */
352int locker__build_mountpoint(locker_context context, locker_attachent *at)
353{
354  char *q, *p;
355  int status;
356
357  status = get_dirlock(context, at, F_RDLCK);
358  if (status)
359    return status;
360
361  if (at->buildfrom)
362    {
363      /* Create any remaining directories. */
364      p = q = at->mountpoint + (strrchr(at->buildfrom, '/') - at->buildfrom);
365      while (!status)
366        {
367          q = strchr(q + 1, '/');
368          if (q)
369            *q = '\0';
370          else if (!(at->fs->flags & LOCKER_FS_NEEDS_MOUNTDIR))
371            break;
372          status = mountpoint_mkdir(context, at, at->mountpoint);
373          if (q)
374            *q = '/';
375          else
376            break;
377        }
378    }
379
380  /* We do not release the dirlock now, to guarantee that no one
381   * else deletes our directories before we're done mounting
382   * the filesystem. It will be released by locker_free_attachent().
383   */
384
385  return status;
386}
387
388/* Remove any directories we created as part of attaching a locker */
389int locker__remove_mountpoint(locker_context context, locker_attachent *at)
390{
391  char *p, *q;
392  int status;
393
394  p = strrchr(at->mountpoint, '/');
395
396  /* If no '/', it must be a MUL locker. */
397  if (!p)
398    return LOCKER_SUCCESS;
399
400  status = get_dirlock(context, at, F_WRLCK);
401  if (status)
402    return status;
403
404  status = mountpoint_rmdir(context, at, at->mountpoint);
405  if (status == LOCKER_ENOENT)
406    status = LOCKER_SUCCESS;
407  while (!status && (q = p) && (q != at->mountpoint))
408    {
409      *q = '\0';
410      p = strrchr(at->mountpoint, '/');
411      status = mountpoint_rmdir(context, at, at->mountpoint);
412      *q = '/';
413    }
414
415  release_dirlock(context, at);
416
417  if (status == LOCKER_ENOENT || status == LOCKER_EMOUNTPOINTBUSY)
418    return LOCKER_SUCCESS;
419  else
420    return status;
421}
422
423/* Lock the dirlock file. type should be F_RDLCK if you are creating
424 * directories and F_WRLCK if you are deleting them. A process that creates
425 * directories should hold onto the lock until it has actually finished
426 * trying to attach the locker, so that some other detach procedure doesn't
427 * remove those directories out from under it.
428 */
429static int get_dirlock(locker_context context, locker_attachent *at, int type)
430{
431  int status;
432  char *lock;
433  struct flock fl;
434
435  lock = locker__attachtab_pathname(context, LOCKER_LOCK, ".dirlock");
436  if (!lock)
437    return LOCKER_ENOMEM;
438
439  at->dirlockfd = open(lock, O_CREAT | O_RDWR, S_IWUSR | S_IRUSR);
440  free(lock);
441  if (at->dirlockfd < 0)
442    {
443      at->dirlockfd = 0;
444      locker__error(context, "%s: Could not %s mountpoint:\n"
445                    "%s while opening directory lock file.\n", at->name,
446                    at->attached ? "remove" : "create", strerror(errno));
447      return LOCKER_EATTACHTAB;
448    }
449  fl.l_type = type;
450  fl.l_whence = SEEK_SET;
451  fl.l_start = fl.l_len = 0;
452  status = fcntl(at->dirlockfd, F_SETLKW, &fl);
453  if (status < 0)
454    {
455      close(at->dirlockfd);
456      at->dirlockfd = 0;
457      locker__error(context, "%s: Could not %s mountpoint:\n"
458                    "%s while locking directory lock file.\n", at->name,
459                    at->attached ? "remove" : "create", strerror(errno));
460      return LOCKER_EATTACHTAB;
461    }
462
463  return LOCKER_SUCCESS;
464}
465
466static void release_dirlock(locker_context context, locker_attachent *at)
467{
468  close(at->dirlockfd);
469  at->dirlockfd = 0;
470}
471
472static int mountpoint_mkdir(locker_context context, locker_attachent *at,
473                            char *path)
474{
475  char *file;
476  struct stat st;
477  mode_t omask;
478  int status;
479
480  file = locker__attachtab_pathname(context, LOCKER_DIRECTORY, path);
481  if (!file)
482    {
483      locker__error(context, "Out of memory building mountpoint.\n");
484      return LOCKER_ENOMEM;
485    }
486
487  if (access(file, F_OK) != 0 || lstat(path, &st) != 0)
488    {
489      int fd;
490
491      /* Need to create the dirfile and the directory. If we get killed
492       * in between the two steps, we'd rather have a dirfile that
493       * doesn't correspond to a directory than a directory that we
494       * don't know is our fault. So do the dirfile first.
495       */
496
497      fd = open(file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
498      if (fd == -1)
499        {
500          free(file);
501          locker__error(context, "Could not create directory data file "
502                        "%s:\n%s.\n", file, strerror(errno));
503          return LOCKER_EATTACHTAB;
504        }
505      close(fd);
506
507      /* Make directory. */
508      omask = umask(0);
509      status = mkdir(path, LOCKER_DIR_MODE);
510      umask(omask);
511      if (status == -1)
512        {
513          if (errno == EEXIST)
514            {
515              locker__error(context, "%s: Directory should not exist: %s\n",
516                            at->name, path);
517            }
518          else
519            {
520              locker__error(context, "%s: Could not create directory %s:\n"
521                            "%s.\n", at->name, path, strerror(errno));
522            }
523          unlink(file);
524          free(file);
525          return LOCKER_EMOUNTPOINT;
526        }
527    }
528  else
529    {
530      if (!S_ISDIR(st.st_mode) || st.st_uid != 0 ||
531          (st.st_mode & MODE_MASK) != LOCKER_DIR_MODE)
532        {
533          locker__error(context, "%s: Directory \"%s\" has changed.\n",
534                        at->name, path);
535          unlink(file);
536          free(file);
537          return LOCKER_EMOUNTPOINT;
538        }
539    }
540
541  free(file);
542  return LOCKER_SUCCESS;
543}
544
545static int mountpoint_rmdir(locker_context context, locker_attachent *at,
546                            char *path)
547{
548  char *file;
549  struct stat st;
550
551  file = locker__attachtab_pathname(context, LOCKER_DIRECTORY, path);
552  if (!file)
553    {
554      locker__error(context, "Out of memory removing mountpoint.\n");
555      return LOCKER_ENOMEM;
556    }
557
558  if (access(file, F_OK) != 0)
559    {
560      free(file);
561      return LOCKER_ENOENT;
562    }
563
564  /* Make sure directory exists and looks like we built it. */
565  if (lstat(path, &st) == -1)
566    {
567      locker__error(context, "%s: Could not check directory %s:\n%s.\n",
568                    at->name, path, strerror(errno));
569      unlink(file);
570      free(file);
571      return LOCKER_EMOUNTPOINT;
572    }
573
574  if (!S_ISDIR(st.st_mode) || st.st_uid != 0 ||
575      (st.st_mode & MODE_MASK) != LOCKER_DIR_MODE)
576    {
577      locker__error(context, "%s: Directory \"%s\" has changed.\n",
578                    at->name, path);
579      unlink(file);
580      free(file);
581      return LOCKER_EMOUNTPOINT;
582    }
583
584  if (rmdir(path) == -1)
585    {
586      free(file);
587      if (errno == EEXIST)
588        return LOCKER_EMOUNTPOINTBUSY;
589      locker__error(context, "%s: Could not remove mountpoint component "
590                    "%s:\n%s.\n", at->name, path, strerror(errno));
591      return LOCKER_EMOUNTPOINT;
592    }
593
594  unlink(file);
595  free(file);
596  return LOCKER_SUCCESS;
597}
Note: See TracBrowser for help on using the repository browser.