LCOV - code coverage report
Current view: top level - source3/modules - vfs_widelinks.c (source / functions) Hit Total Coverage
Test: coverage report for abartlet/fix-coverage dd10fb34 Lines: 81 111 73.0 %
Date: 2021-09-23 10:06:22 Functions: 9 9 100.0 %

          Line data    Source code
       1             : /*
       2             :  * Widelinks VFS module. Causes smbd not to see symlinks.
       3             :  *
       4             :  * Copyright (C) Jeremy Allison, 2020
       5             :  *
       6             :  * This program is free software; you can redistribute it and/or modify
       7             :  * it under the terms of the GNU General Public License as published by
       8             :  * the Free Software Foundation; either version 3 of the License, or
       9             :  * (at your option) any later version.
      10             :  *
      11             :  * This program is distributed in the hope that it will be useful,
      12             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             :  * GNU General Public License for more details.
      15             :  *
      16             :  * You should have received a copy of the GNU General Public License
      17             :  * along with this program; if not, see <http://www.gnu.org/licenses/>.
      18             :  */
      19             : 
      20             : /*
      21             :  What does this module do ? It implements the explicitly insecure
      22             :  "widelinks = yes" functionality that used to be in the core smbd
      23             :  code.
      24             : 
      25             :  Now this is implemented here, the insecure share-escape code that
      26             :  explicitly allows escape from an exported share path can be removed
      27             :  from smbd, leaving it a cleaner and more maintainable code base.
      28             : 
      29             :  The smbd code can now always return ACCESS_DENIED if a path
      30             :  leads outside a share.
      31             : 
      32             :  How does it do that ? There are 2 features.
      33             : 
      34             :  1). When the upper layer code does a chdir() call to a pathname,
      35             :  this module stores the requested pathname inside config->cwd.
      36             : 
      37             :  When the upper layer code does a getwd() or realpath(), we return
      38             :  the absolute path of the value stored in config->cwd, *not* the
      39             :  position on the underlying filesystem.
      40             : 
      41             :  This hides symlinks as if the chdir pathname contains a symlink,
      42             :  normally doing a realpath call on it would return the real
      43             :  position on the filesystem. For widelinks = yes, this isn't what
      44             :  you want. You want the position you think is underneath the share
      45             :  definition - the symlink path you used to go outside the share,
      46             :  not the contents of the symlink itself.
      47             : 
      48             :  That way, the upper layer smbd code can strictly enforce paths
      49             :  being underneath a share definition without the knowledge that
      50             :  "widelinks = yes" has moved us outside the share definition.
      51             : 
      52             :  1a). Note that when setting up a share, smbd may make calls such
      53             :  as realpath and stat/lstat in order to set up the share definition.
      54             :  These calls are made *before* smbd calls chdir() to move the working
      55             :  directory below the exported share definition. In order to allow
      56             :  this, all the vfs_widelinks functions are coded to just pass through
      57             :  the vfs call to the next module in the chain if (a). The widelinks
      58             :  module was loaded in error by an administrator and widelinks is
      59             :  set to "no". This is the:
      60             : 
      61             :         if (!config->active) {
      62             :                 Module not active.
      63             :                 SMB_VFS_NEXT_XXXXX(...)
      64             :         }
      65             : 
      66             :  idiom in the vfs functions.
      67             : 
      68             :  1b). If the module was correctly active, but smbd has yet
      69             :  to call chdir(), then config->cwd == NULL. In that case
      70             :  the correct action (to match the previous widelinks behavior
      71             :  in the code inside smbd) is to pass through the vfs call to
      72             :  the next module in the chain. That way, any symlinks in the
      73             :  pathname are still exposed to smbd, which will restrict them to
      74             :  be under the exported share definition. This allows the module
      75             :  to "fail safe" for any vfs call made when setting up the share
      76             :  structure definition, rather than fail unsafe by hiding symlinks
      77             :  before chdir is called. This is the:
      78             : 
      79             :         if (config->cwd == NULL) {
      80             :                 XXXXX syscall before chdir - see note 1b above.
      81             :                 return SMB_VFS_NEXT_XXXXX()
      82             :         }
      83             : 
      84             :  idiom in the vfs functions.
      85             : 
      86             :  2). The module hides the existance of symlinks by inside
      87             :  lstat(), open(), and readdir() so long as it's not a POSIX
      88             :  pathname request (those requests *must* be aware of symlinks
      89             :  and the POSIX client has to follow them, it's expected that
      90             :  a server will always fail to follow symlinks).
      91             : 
      92             :  It does this by:
      93             : 
      94             :  2a). lstat -> stat
      95             :  2b). open removes any O_NOFOLLOW from flags.
      96             :  2c). The optimization in readdir that returns a stat
      97             :  struct is removed as this could return a symlink mode
      98             :  bit, causing smbd to always call stat/lstat itself on
      99             :  a pathname (which we'll then use to hide symlinks).
     100             : 
     101             : */
     102             : 
     103             : #include "includes.h"
     104             : #include "smbd/smbd.h"
     105             : #include "lib/util_path.h"
     106             : 
     107             : struct widelinks_config {
     108             :         bool active;
     109             :         char *cwd;
     110             : };
     111             : 
     112         208 : static int widelinks_connect(struct vfs_handle_struct *handle,
     113             :                         const char *service,
     114             :                         const char *user)
     115             : {
     116             :         struct widelinks_config *config;
     117             :         int ret;
     118             : 
     119         208 :         ret = SMB_VFS_NEXT_CONNECT(handle,
     120             :                                 service,
     121             :                                 user);
     122         208 :         if (ret != 0) {
     123           0 :                 return ret;
     124             :         }
     125             : 
     126         208 :         config = talloc_zero(handle->conn,
     127             :                                 struct widelinks_config);
     128         208 :         if (!config) {
     129           0 :                 SMB_VFS_NEXT_DISCONNECT(handle);
     130           0 :                 return -1;
     131             :         }
     132         208 :         config->active = lp_widelinks(SNUM(handle->conn));
     133         208 :         if (!config->active) {
     134           0 :                 DBG_ERR("vfs_widelinks module loaded with "
     135             :                         "widelinks = no\n");
     136             :         }
     137             : 
     138         208 :         SMB_VFS_HANDLE_SET_DATA(handle,
     139             :                                 config,
     140             :                                 NULL, /* free_fn */
     141             :                                 struct widelinks_config,
     142             :                                 return -1);
     143         208 :         return 0;
     144             : }
     145             : 
     146        2714 : static int widelinks_chdir(struct vfs_handle_struct *handle,
     147             :                                 const struct smb_filename *smb_fname)
     148             : {
     149        2714 :         int ret = -1;
     150        2714 :         struct widelinks_config *config = NULL;
     151        2714 :         char *new_cwd = NULL;
     152             : 
     153        2714 :         SMB_VFS_HANDLE_GET_DATA(handle,
     154             :                                 config,
     155             :                                 struct widelinks_config,
     156             :                                 return -1);
     157             : 
     158        2714 :         if (!config->active) {
     159             :                 /* Module not active. */
     160           0 :                 return SMB_VFS_NEXT_CHDIR(handle, smb_fname);
     161             :         }
     162             : 
     163             :         /*
     164             :          * We know we never get a path containing
     165             :          * DOT or DOTDOT.
     166             :          */
     167             : 
     168        2714 :         if (smb_fname->base_name[0] == '/') {
     169             :                 /* Absolute path - replace. */
     170        2194 :                 new_cwd = talloc_strdup(config,
     171        2194 :                                 smb_fname->base_name);
     172             :         } else {
     173         520 :                 if (config->cwd == NULL) {
     174             :                         /*
     175             :                          * Relative chdir before absolute one -
     176             :                          * see note 1b above.
     177             :                          */
     178           0 :                         struct smb_filename *current_dir_fname =
     179           0 :                                         SMB_VFS_NEXT_GETWD(handle,
     180             :                                                         config);
     181           0 :                         if (current_dir_fname == NULL) {
     182           0 :                                 return -1;
     183             :                         }
     184             :                         /* Paranoia.. */
     185           0 :                         if (current_dir_fname->base_name[0] != '/') {
     186           0 :                                 DBG_ERR("SMB_VFS_NEXT_GETWD returned "
     187             :                                         "non-absolute path |%s|\n",
     188             :                                         current_dir_fname->base_name);
     189           0 :                                 TALLOC_FREE(current_dir_fname);
     190           0 :                                 return -1;
     191             :                         }
     192           0 :                         config->cwd = talloc_strdup(config,
     193           0 :                                         current_dir_fname->base_name);
     194           0 :                         TALLOC_FREE(current_dir_fname);
     195           0 :                         if (config->cwd == NULL) {
     196           0 :                                 return -1;
     197             :                         }
     198             :                 }
     199         520 :                 new_cwd = talloc_asprintf(config,
     200             :                                 "%s/%s",
     201             :                                 config->cwd,
     202           0 :                                 smb_fname->base_name);
     203             :         }
     204        2714 :         if (new_cwd == NULL) {
     205           0 :                 return -1;
     206             :         }
     207        2714 :         ret = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
     208        2714 :         if (ret == -1) {
     209         198 :                 TALLOC_FREE(new_cwd);
     210         198 :                 return ret;
     211             :         }
     212             :         /* Replace the cache we use for realpath/getwd. */
     213        2516 :         TALLOC_FREE(config->cwd);
     214        2516 :         config->cwd = new_cwd;
     215        2516 :         DBG_DEBUG("config->cwd now |%s|\n", config->cwd);
     216        2516 :         return 0;
     217             : }
     218             : 
     219        3064 : static struct smb_filename *widelinks_getwd(vfs_handle_struct *handle,
     220             :                                 TALLOC_CTX *ctx)
     221             : {
     222        3064 :         struct widelinks_config *config = NULL;
     223             : 
     224        3064 :         SMB_VFS_HANDLE_GET_DATA(handle,
     225             :                                 config,
     226             :                                 struct widelinks_config,
     227             :                                 return NULL);
     228             : 
     229        3064 :         if (!config->active) {
     230             :                 /* Module not active. */
     231           0 :                 return SMB_VFS_NEXT_GETWD(handle, ctx);
     232             :         }
     233        3064 :         if (config->cwd == NULL) {
     234             :                 /* getwd before chdir. See note 1b above. */
     235           0 :                 return SMB_VFS_NEXT_GETWD(handle, ctx);
     236             :         }
     237        3064 :         return synthetic_smb_fname(ctx,
     238        3064 :                                 config->cwd,
     239             :                                 NULL,
     240             :                                 NULL,
     241             :                                 0,
     242             :                                 0);
     243             : }
     244             : 
     245        1988 : static struct smb_filename *widelinks_realpath(vfs_handle_struct *handle,
     246             :                         TALLOC_CTX *ctx,
     247             :                         const struct smb_filename *smb_fname_in)
     248             : {
     249        1988 :         struct widelinks_config *config = NULL;
     250        1988 :         char *pathname = NULL;
     251        1988 :         char *resolved_pathname = NULL;
     252             :         struct smb_filename *smb_fname;
     253             : 
     254        1988 :         SMB_VFS_HANDLE_GET_DATA(handle,
     255             :                                 config,
     256             :                                 struct widelinks_config,
     257             :                                 return NULL);
     258             : 
     259        1988 :         if (!config->active) {
     260             :                 /* Module not active. */
     261           0 :                 return SMB_VFS_NEXT_REALPATH(handle,
     262             :                                 ctx,
     263             :                                 smb_fname_in);
     264             :         }
     265             : 
     266        1988 :         if (config->cwd == NULL) {
     267             :                 /* realpath before chdir. See note 1b above. */
     268         416 :                 return SMB_VFS_NEXT_REALPATH(handle,
     269             :                                 ctx,
     270             :                                 smb_fname_in);
     271             :         }
     272             : 
     273        1572 :         if (smb_fname_in->base_name[0] == '/') {
     274             :                 /* Absolute path - process as-is. */
     275         312 :                 pathname = talloc_strdup(config,
     276         312 :                                         smb_fname_in->base_name);
     277             :         } else {
     278             :                 /* Relative path - most commonly "." */
     279        1260 :                 pathname = talloc_asprintf(config,
     280             :                                 "%s/%s",
     281             :                                 config->cwd,
     282           0 :                                 smb_fname_in->base_name);
     283             :         }
     284             : 
     285        1572 :         SMB_ASSERT(pathname[0] == '/');
     286             : 
     287        1572 :         resolved_pathname = canonicalize_absolute_path(config, pathname);
     288        1572 :         if (resolved_pathname == NULL) {
     289           0 :                 TALLOC_FREE(pathname);
     290           0 :                 return NULL;
     291             :         }
     292             : 
     293        1572 :         DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n",
     294             :                         smb_fname_in->base_name,
     295             :                         pathname,
     296             :                         resolved_pathname);
     297             : 
     298        1572 :         smb_fname = synthetic_smb_fname(ctx,
     299             :                                 resolved_pathname,
     300             :                                 NULL,
     301             :                                 NULL,
     302             :                                 0,
     303             :                                 0);
     304        1572 :         TALLOC_FREE(pathname);
     305        1572 :         TALLOC_FREE(resolved_pathname);
     306        1572 :         return smb_fname;
     307             : }
     308             : 
     309        3510 : static int widelinks_lstat(vfs_handle_struct *handle,
     310             :                         struct smb_filename *smb_fname)
     311             : {
     312        3510 :         struct widelinks_config *config = NULL;
     313             : 
     314        3510 :         SMB_VFS_HANDLE_GET_DATA(handle,
     315             :                                 config,
     316             :                                 struct widelinks_config,
     317             :                                 return -1);
     318             : 
     319        3510 :         if (!config->active) {
     320             :                 /* Module not active. */
     321           0 :                 return SMB_VFS_NEXT_LSTAT(handle,
     322             :                                 smb_fname);
     323             :         }
     324             : 
     325        3510 :         if (config->cwd == NULL) {
     326             :                 /* lstat before chdir. See note 1b above. */
     327           0 :                 return SMB_VFS_NEXT_LSTAT(handle,
     328             :                                 smb_fname);
     329             :         }
     330             : 
     331        3510 :         if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
     332             :                 /* POSIX sees symlinks. */
     333           0 :                 return SMB_VFS_NEXT_LSTAT(handle,
     334             :                                 smb_fname);
     335             :         }
     336             : 
     337             :         /* Replace with STAT. */
     338        3510 :         return SMB_VFS_NEXT_STAT(handle, smb_fname);
     339             : }
     340             : 
     341        1848 : static int widelinks_openat(vfs_handle_struct *handle,
     342             :                             const struct files_struct *dirfsp,
     343             :                             const struct smb_filename *smb_fname,
     344             :                             files_struct *fsp,
     345             :                             int flags,
     346             :                             mode_t mode)
     347             : {
     348        1848 :         struct widelinks_config *config = NULL;
     349             : 
     350        1848 :         SMB_VFS_HANDLE_GET_DATA(handle,
     351             :                                 config,
     352             :                                 struct widelinks_config,
     353             :                                 return -1);
     354             : 
     355        3696 :         if (config->active &&
     356        3696 :             (config->cwd != NULL) &&
     357        1848 :             !(smb_fname->flags & SMB_FILENAME_POSIX_PATH))
     358             :         {
     359             :                 /*
     360             :                  * Module active, openat after chdir (see note 1b above) and not
     361             :                  * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW.
     362             :                  */
     363        1848 :                 flags = (flags & ~O_NOFOLLOW);
     364             :         }
     365             : 
     366        1848 :         return SMB_VFS_NEXT_OPENAT(handle,
     367             :                                    dirfsp,
     368             :                                    smb_fname,
     369             :                                    fsp,
     370             :                                    flags,
     371             :                                    mode);
     372             : }
     373             : 
     374        1236 : static struct dirent *widelinks_readdir(vfs_handle_struct *handle,
     375             :                                         struct files_struct *dirfsp,
     376             :                                         DIR *dirp,
     377             :                                         SMB_STRUCT_STAT *sbuf)
     378             : {
     379        1236 :         struct widelinks_config *config = NULL;
     380             :         struct dirent *result;
     381             : 
     382        1236 :         SMB_VFS_HANDLE_GET_DATA(handle,
     383             :                                 config,
     384             :                                 struct widelinks_config,
     385             :                                 return NULL);
     386             : 
     387        1236 :         result = SMB_VFS_NEXT_READDIR(handle,
     388             :                                       dirfsp,
     389             :                                       dirp,
     390             :                                       sbuf);
     391             : 
     392        1236 :         if (!config->active) {
     393             :                 /* Module not active. */
     394           0 :                 return result;
     395             :         }
     396             : 
     397             :         /*
     398             :          * Prevent optimization of returning
     399             :          * the stat info. Force caller to go
     400             :          * through our LSTAT that hides symlinks.
     401             :          */
     402             : 
     403        1236 :         if (sbuf) {
     404          84 :                 SET_STAT_INVALID(*sbuf);
     405             :         }
     406        1236 :         return result;
     407             : }
     408             : 
     409             : static struct vfs_fn_pointers vfs_widelinks_fns = {
     410             :         .connect_fn = widelinks_connect,
     411             : 
     412             :         .openat_fn = widelinks_openat,
     413             :         .lstat_fn = widelinks_lstat,
     414             :         /*
     415             :          * NB. We don't need an lchown function as this
     416             :          * is only called (a) on directory create and
     417             :          * (b) on POSIX extensions names.
     418             :          */
     419             :         .chdir_fn = widelinks_chdir,
     420             :         .getwd_fn = widelinks_getwd,
     421             :         .realpath_fn = widelinks_realpath,
     422             :         .readdir_fn = widelinks_readdir
     423             : };
     424             : 
     425             : static_decl_vfs;
     426         222 : NTSTATUS vfs_widelinks_init(TALLOC_CTX *ctx)
     427             : {
     428         222 :         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
     429             :                                 "widelinks",
     430             :                                 &vfs_widelinks_fns);
     431             : }

Generated by: LCOV version 1.13