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
{
112 static int widelinks_connect(struct vfs_handle_struct
*handle
,
116 struct widelinks_config
*config
;
119 ret
= SMB_VFS_NEXT_CONNECT(handle
,
126 config
= talloc_zero(handle
->conn
,
127 struct widelinks_config
);
129 SMB_VFS_NEXT_DISCONNECT(handle
);
132 config
->active
= lp_widelinks(SNUM(handle
->conn
));
133 if (!config
->active
) {
134 DBG_ERR("vfs_widelinks module loaded with "
138 SMB_VFS_HANDLE_SET_DATA(handle
,
141 struct widelinks_config
,
146 static int widelinks_chdir(struct vfs_handle_struct
*handle
,
147 const struct smb_filename
*smb_fname
)
150 struct widelinks_config
*config
= NULL
;
151 char *new_cwd
= NULL
;
153 SMB_VFS_HANDLE_GET_DATA(handle
,
155 struct widelinks_config
,
158 if (!config
->active
) {
159 /* Module not active. */
160 return SMB_VFS_NEXT_CHDIR(handle
, smb_fname
);
164 * We know we never get a path containing
168 if (smb_fname
->base_name
[0] == '/') {
169 /* Absolute path - replace. */
170 new_cwd
= talloc_strdup(config
,
171 smb_fname
->base_name
);
173 if (config
->cwd
== NULL
) {
175 * Relative chdir before absolute one -
178 struct smb_filename
*current_dir_fname
=
179 SMB_VFS_NEXT_GETWD(handle
,
181 if (current_dir_fname
== NULL
) {
185 if (current_dir_fname
->base_name
[0] != '/') {
186 DBG_ERR("SMB_VFS_NEXT_GETWD returned "
187 "non-absolute path |%s|\n",
188 current_dir_fname
->base_name
);
189 TALLOC_FREE(current_dir_fname
);
192 config
->cwd
= talloc_strdup(config
,
193 current_dir_fname
->base_name
);
194 TALLOC_FREE(current_dir_fname
);
195 if (config
->cwd
== NULL
) {
199 new_cwd
= talloc_asprintf(config
,
202 smb_fname
->base_name
);
204 if (new_cwd
== NULL
) {
207 ret
= SMB_VFS_NEXT_CHDIR(handle
, smb_fname
);
209 TALLOC_FREE(new_cwd
);
212 /* Replace the cache we use for realpath/getwd. */
213 TALLOC_FREE(config
->cwd
);
214 config
->cwd
= new_cwd
;
215 DBG_DEBUG("config->cwd now |%s|\n", config
->cwd
);
219 static struct smb_filename
*widelinks_getwd(vfs_handle_struct
*handle
,
222 struct widelinks_config
*config
= NULL
;
224 SMB_VFS_HANDLE_GET_DATA(handle
,
226 struct widelinks_config
,
229 if (!config
->active
) {
230 /* Module not active. */
231 return SMB_VFS_NEXT_GETWD(handle
, ctx
);
233 if (config
->cwd
== NULL
) {
234 /* getwd before chdir. See note 1b above. */
235 return SMB_VFS_NEXT_GETWD(handle
, ctx
);
237 return synthetic_smb_fname(ctx
,
245 static struct smb_filename
*widelinks_realpath(vfs_handle_struct
*handle
,
247 const struct smb_filename
*smb_fname_in
)
249 struct widelinks_config
*config
= NULL
;
250 char *pathname
= NULL
;
251 char *resolved_pathname
= NULL
;
252 struct smb_filename
*smb_fname
;
254 SMB_VFS_HANDLE_GET_DATA(handle
,
256 struct widelinks_config
,
259 if (!config
->active
) {
260 /* Module not active. */
261 return SMB_VFS_NEXT_REALPATH(handle
,
266 if (config
->cwd
== NULL
) {
267 /* realpath before chdir. See note 1b above. */
268 return SMB_VFS_NEXT_REALPATH(handle
,
273 if (smb_fname_in
->base_name
[0] == '/') {
274 /* Absolute path - process as-is. */
275 pathname
= talloc_strdup(config
,
276 smb_fname_in
->base_name
);
278 /* Relative path - most commonly "." */
279 pathname
= talloc_asprintf(config
,
282 smb_fname_in
->base_name
);
285 SMB_ASSERT(pathname
[0] == '/');
287 resolved_pathname
= canonicalize_absolute_path(config
, pathname
);
288 if (resolved_pathname
== NULL
) {
289 TALLOC_FREE(pathname
);
293 DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n",
294 smb_fname_in
->base_name
,
298 smb_fname
= synthetic_smb_fname(ctx
,
304 TALLOC_FREE(pathname
);
305 TALLOC_FREE(resolved_pathname
);
309 static int widelinks_lstat(vfs_handle_struct
*handle
,
310 struct smb_filename
*smb_fname
)
312 struct widelinks_config
*config
= NULL
;
314 SMB_VFS_HANDLE_GET_DATA(handle
,
316 struct widelinks_config
,
319 if (!config
->active
) {
320 /* Module not active. */
321 return SMB_VFS_NEXT_LSTAT(handle
,
325 if (config
->cwd
== NULL
) {
326 /* lstat before chdir. See note 1b above. */
327 return SMB_VFS_NEXT_LSTAT(handle
,
331 if (smb_fname
->flags
& SMB_FILENAME_POSIX_PATH
) {
332 /* POSIX sees symlinks. */
333 return SMB_VFS_NEXT_LSTAT(handle
,
337 /* Replace with STAT. */
338 return SMB_VFS_NEXT_STAT(handle
, smb_fname
);
341 static int widelinks_openat(vfs_handle_struct
*handle
,
342 const struct files_struct
*dirfsp
,
343 const struct smb_filename
*smb_fname
,
345 const struct vfs_open_how
*_how
)
347 struct vfs_open_how how
= *_how
;
348 struct widelinks_config
*config
= NULL
;
350 SMB_VFS_HANDLE_GET_DATA(handle
,
352 struct widelinks_config
,
355 if (config
->active
&&
356 (config
->cwd
!= NULL
) &&
357 !(smb_fname
->flags
& SMB_FILENAME_POSIX_PATH
))
360 * Module active, openat after chdir (see note 1b above) and not
361 * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW.
363 how
.flags
= (how
.flags
& ~O_NOFOLLOW
);
366 return SMB_VFS_NEXT_OPENAT(handle
,
373 static struct dirent
*widelinks_readdir(vfs_handle_struct
*handle
,
374 struct files_struct
*dirfsp
,
376 SMB_STRUCT_STAT
*sbuf
)
378 struct widelinks_config
*config
= NULL
;
379 struct dirent
*result
;
381 SMB_VFS_HANDLE_GET_DATA(handle
,
383 struct widelinks_config
,
386 result
= SMB_VFS_NEXT_READDIR(handle
,
391 if (!config
->active
) {
392 /* Module not active. */
397 * Prevent optimization of returning
398 * the stat info. Force caller to go
399 * through our LSTAT that hides symlinks.
403 SET_STAT_INVALID(*sbuf
);
408 static struct vfs_fn_pointers vfs_widelinks_fns
= {
409 .connect_fn
= widelinks_connect
,
411 .openat_fn
= widelinks_openat
,
412 .lstat_fn
= widelinks_lstat
,
414 * NB. We don't need an lchown function as this
415 * is only called (a) on directory create and
416 * (b) on POSIX extensions names.
418 .chdir_fn
= widelinks_chdir
,
419 .getwd_fn
= widelinks_getwd
,
420 .realpath_fn
= widelinks_realpath
,
421 .readdir_fn
= widelinks_readdir
425 NTSTATUS
vfs_widelinks_init(TALLOC_CTX
*ctx
)
427 return smb_register_vfs(SMB_VFS_INTERFACE_VERSION
,