2 * Widelinks VFS module. Causes smbd not to see symlinks.
4 * Copyright (C) Jeremy Allison, 2020
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.
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.
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/>.
21 What does this module do ? It implements the explicitly insecure
22 "widelinks = yes" functionality that used to be in the core smbd
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.
29 The smbd code can now always return ACCESS_DENIED if a path
30 leads outside a share.
32 How does it do that ? There are 2 features.
34 1). When the upper layer code does a chdir() call to a pathname,
35 this module stores the requested pathname inside config->cwd.
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.
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.
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.
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:
61 if (!config->active) {
63 SMB_VFS_NEXT_XXXXX(...)
66 idiom in the vfs functions.
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:
79 if (config->cwd == NULL) {
80 XXXXX syscall before chdir - see note 1b above.
81 return SMB_VFS_NEXT_XXXXX()
84 idiom in the vfs functions.
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).
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).
103 #include "includes.h"
104 #include "smbd/smbd.h"
105 #include "lib/util_path.h"
107 struct widelinks_config
{
113 static int widelinks_connect(struct vfs_handle_struct
*handle
,
117 struct widelinks_config
*config
;
120 ret
= SMB_VFS_NEXT_CONNECT(handle
,
127 config
= talloc_zero(handle
->conn
,
128 struct widelinks_config
);
130 SMB_VFS_NEXT_DISCONNECT(handle
);
133 config
->active
= lp_widelinks(SNUM(handle
->conn
));
134 if (!config
->active
) {
135 DBG_ERR("vfs_widelinks module loaded with "
138 config
->is_dfs_share
=
139 (lp_host_msdfs() && lp_msdfs_root(SNUM(handle
->conn
)));
140 SMB_VFS_HANDLE_SET_DATA(handle
,
143 struct widelinks_config
,
148 static int widelinks_chdir(struct vfs_handle_struct
*handle
,
149 const struct smb_filename
*smb_fname
)
152 struct widelinks_config
*config
= NULL
;
153 char *new_cwd
= NULL
;
155 SMB_VFS_HANDLE_GET_DATA(handle
,
157 struct widelinks_config
,
160 if (!config
->active
) {
161 /* Module not active. */
162 return SMB_VFS_NEXT_CHDIR(handle
, smb_fname
);
166 * We know we never get a path containing
170 if (smb_fname
->base_name
[0] == '/') {
171 /* Absolute path - replace. */
172 new_cwd
= talloc_strdup(config
,
173 smb_fname
->base_name
);
175 if (config
->cwd
== NULL
) {
177 * Relative chdir before absolute one -
180 struct smb_filename
*current_dir_fname
=
181 SMB_VFS_NEXT_GETWD(handle
,
183 if (current_dir_fname
== NULL
) {
187 if (current_dir_fname
->base_name
[0] != '/') {
188 DBG_ERR("SMB_VFS_NEXT_GETWD returned "
189 "non-absolute path |%s|\n",
190 current_dir_fname
->base_name
);
191 TALLOC_FREE(current_dir_fname
);
194 config
->cwd
= talloc_strdup(config
,
195 current_dir_fname
->base_name
);
196 TALLOC_FREE(current_dir_fname
);
197 if (config
->cwd
== NULL
) {
201 new_cwd
= talloc_asprintf(config
,
204 smb_fname
->base_name
);
206 if (new_cwd
== NULL
) {
209 ret
= SMB_VFS_NEXT_CHDIR(handle
, smb_fname
);
211 TALLOC_FREE(new_cwd
);
214 /* Replace the cache we use for realpath/getwd. */
215 TALLOC_FREE(config
->cwd
);
216 config
->cwd
= new_cwd
;
217 DBG_DEBUG("config->cwd now |%s|\n", config
->cwd
);
221 static struct smb_filename
*widelinks_getwd(vfs_handle_struct
*handle
,
224 struct widelinks_config
*config
= NULL
;
226 SMB_VFS_HANDLE_GET_DATA(handle
,
228 struct widelinks_config
,
231 if (!config
->active
) {
232 /* Module not active. */
233 return SMB_VFS_NEXT_GETWD(handle
, ctx
);
235 if (config
->cwd
== NULL
) {
236 /* getwd before chdir. See note 1b above. */
237 return SMB_VFS_NEXT_GETWD(handle
, ctx
);
239 return synthetic_smb_fname(ctx
,
247 static struct smb_filename
*widelinks_realpath(vfs_handle_struct
*handle
,
249 const struct smb_filename
*smb_fname_in
)
251 struct widelinks_config
*config
= NULL
;
252 char *pathname
= NULL
;
253 char *resolved_pathname
= NULL
;
254 struct smb_filename
*smb_fname
;
256 SMB_VFS_HANDLE_GET_DATA(handle
,
258 struct widelinks_config
,
261 if (!config
->active
) {
262 /* Module not active. */
263 return SMB_VFS_NEXT_REALPATH(handle
,
268 if (config
->cwd
== NULL
) {
269 /* realpath before chdir. See note 1b above. */
270 return SMB_VFS_NEXT_REALPATH(handle
,
275 if (smb_fname_in
->base_name
[0] == '/') {
276 /* Absolute path - process as-is. */
277 pathname
= talloc_strdup(config
,
278 smb_fname_in
->base_name
);
280 /* Relative path - most commonly "." */
281 pathname
= talloc_asprintf(config
,
284 smb_fname_in
->base_name
);
287 SMB_ASSERT(pathname
[0] == '/');
289 resolved_pathname
= canonicalize_absolute_path(config
, pathname
);
290 if (resolved_pathname
== NULL
) {
291 TALLOC_FREE(pathname
);
295 DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n",
296 smb_fname_in
->base_name
,
300 smb_fname
= synthetic_smb_fname(ctx
,
306 TALLOC_FREE(pathname
);
307 TALLOC_FREE(resolved_pathname
);
311 static int widelinks_lstat(vfs_handle_struct
*handle
,
312 struct smb_filename
*smb_fname
)
314 struct widelinks_config
*config
= NULL
;
316 SMB_VFS_HANDLE_GET_DATA(handle
,
318 struct widelinks_config
,
321 if (!config
->active
) {
322 /* Module not active. */
323 return SMB_VFS_NEXT_LSTAT(handle
,
327 if (config
->cwd
== NULL
) {
328 /* lstat before chdir. See note 1b above. */
329 return SMB_VFS_NEXT_LSTAT(handle
,
333 if (smb_fname
->flags
& SMB_FILENAME_POSIX_PATH
) {
334 /* POSIX sees symlinks. */
335 return SMB_VFS_NEXT_LSTAT(handle
,
339 /* Replace with STAT. */
340 return SMB_VFS_NEXT_STAT(handle
, smb_fname
);
343 static int widelinks_openat(vfs_handle_struct
*handle
,
344 const struct files_struct
*dirfsp
,
345 const struct smb_filename
*smb_fname
,
347 const struct vfs_open_how
*_how
)
349 struct vfs_open_how how
= *_how
;
350 struct widelinks_config
*config
= NULL
;
352 SMB_VFS_HANDLE_GET_DATA(handle
,
354 struct widelinks_config
,
357 if (config
->active
&&
358 (config
->cwd
!= NULL
) &&
359 !(smb_fname
->flags
& SMB_FILENAME_POSIX_PATH
))
362 * Module active, openat after chdir (see note 1b above) and not
363 * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW.
365 how
.flags
= (how
.flags
& ~O_NOFOLLOW
);
368 return SMB_VFS_NEXT_OPENAT(handle
,
375 static struct dirent
*widelinks_readdir(vfs_handle_struct
*handle
,
376 struct files_struct
*dirfsp
,
378 SMB_STRUCT_STAT
*sbuf
)
380 struct widelinks_config
*config
= NULL
;
381 struct dirent
*result
;
383 SMB_VFS_HANDLE_GET_DATA(handle
,
385 struct widelinks_config
,
388 result
= SMB_VFS_NEXT_READDIR(handle
,
393 if (!config
->active
) {
394 /* Module not active. */
399 * Prevent optimization of returning
400 * the stat info. Force caller to go
401 * through our LSTAT that hides symlinks.
405 SET_STAT_INVALID(*sbuf
);
410 static struct vfs_fn_pointers vfs_widelinks_fns
= {
411 .connect_fn
= widelinks_connect
,
413 .openat_fn
= widelinks_openat
,
414 .lstat_fn
= widelinks_lstat
,
416 * NB. We don't need an lchown function as this
417 * is only called (a) on directory create and
418 * (b) on POSIX extensions names.
420 .chdir_fn
= widelinks_chdir
,
421 .getwd_fn
= widelinks_getwd
,
422 .realpath_fn
= widelinks_realpath
,
423 .readdir_fn
= widelinks_readdir
427 NTSTATUS
vfs_widelinks_init(TALLOC_CTX
*ctx
)
429 return smb_register_vfs(SMB_VFS_INTERFACE_VERSION
,