utils: Fix up 14a533680245
[samba4-gss.git] / source3 / modules / vfs_widelinks.c
blob4339f6de9e00a55502bad1b8d90f408aa991e0a8
1 /*
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
23 code.
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) {
62 Module not 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 existence 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).
92 It does this by:
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).
103 #include "includes.h"
104 #include "smbd/smbd.h"
105 #include "lib/util_path.h"
107 struct widelinks_config {
108 bool active;
109 bool is_dfs_share;
110 char *cwd;
113 static int widelinks_connect(struct vfs_handle_struct *handle,
114 const char *service,
115 const char *user)
117 struct widelinks_config *config;
118 int ret;
120 ret = SMB_VFS_NEXT_CONNECT(handle,
121 service,
122 user);
123 if (ret != 0) {
124 return ret;
127 config = talloc_zero(handle->conn,
128 struct widelinks_config);
129 if (!config) {
130 SMB_VFS_NEXT_DISCONNECT(handle);
131 return -1;
133 config->active = lp_widelinks(SNUM(handle->conn));
134 if (!config->active) {
135 DBG_ERR("vfs_widelinks module loaded with "
136 "widelinks = no\n");
138 config->is_dfs_share =
139 (lp_host_msdfs() && lp_msdfs_root(SNUM(handle->conn)));
140 SMB_VFS_HANDLE_SET_DATA(handle,
141 config,
142 NULL, /* free_fn */
143 struct widelinks_config,
144 return -1);
145 return 0;
148 static int widelinks_chdir(struct vfs_handle_struct *handle,
149 const struct smb_filename *smb_fname)
151 int ret = -1;
152 struct widelinks_config *config = NULL;
153 char *new_cwd = NULL;
155 SMB_VFS_HANDLE_GET_DATA(handle,
156 config,
157 struct widelinks_config,
158 return -1);
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
167 * DOT or DOTDOT.
170 if (smb_fname->base_name[0] == '/') {
171 /* Absolute path - replace. */
172 new_cwd = talloc_strdup(config,
173 smb_fname->base_name);
174 } else {
175 if (config->cwd == NULL) {
177 * Relative chdir before absolute one -
178 * see note 1b above.
180 struct smb_filename *current_dir_fname =
181 SMB_VFS_NEXT_GETWD(handle,
182 config);
183 if (current_dir_fname == NULL) {
184 return -1;
186 /* Paranoia.. */
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);
192 return -1;
194 config->cwd = talloc_strdup(config,
195 current_dir_fname->base_name);
196 TALLOC_FREE(current_dir_fname);
197 if (config->cwd == NULL) {
198 return -1;
201 new_cwd = talloc_asprintf(config,
202 "%s/%s",
203 config->cwd,
204 smb_fname->base_name);
206 if (new_cwd == NULL) {
207 return -1;
209 ret = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
210 if (ret == -1) {
211 TALLOC_FREE(new_cwd);
212 return ret;
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);
218 return 0;
221 static struct smb_filename *widelinks_getwd(vfs_handle_struct *handle,
222 TALLOC_CTX *ctx)
224 struct widelinks_config *config = NULL;
226 SMB_VFS_HANDLE_GET_DATA(handle,
227 config,
228 struct widelinks_config,
229 return NULL);
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,
240 config->cwd,
241 NULL,
242 NULL,
247 static struct smb_filename *widelinks_realpath(vfs_handle_struct *handle,
248 TALLOC_CTX *ctx,
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,
257 config,
258 struct widelinks_config,
259 return NULL);
261 if (!config->active) {
262 /* Module not active. */
263 return SMB_VFS_NEXT_REALPATH(handle,
264 ctx,
265 smb_fname_in);
268 if (config->cwd == NULL) {
269 /* realpath before chdir. See note 1b above. */
270 return SMB_VFS_NEXT_REALPATH(handle,
271 ctx,
272 smb_fname_in);
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);
279 } else {
280 /* Relative path - most commonly "." */
281 pathname = talloc_asprintf(config,
282 "%s/%s",
283 config->cwd,
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);
292 return NULL;
295 DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n",
296 smb_fname_in->base_name,
297 pathname,
298 resolved_pathname);
300 smb_fname = synthetic_smb_fname(ctx,
301 resolved_pathname,
302 NULL,
303 NULL,
306 TALLOC_FREE(pathname);
307 TALLOC_FREE(resolved_pathname);
308 return smb_fname;
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,
317 config,
318 struct widelinks_config,
319 return -1);
321 if (!config->active) {
322 /* Module not active. */
323 return SMB_VFS_NEXT_LSTAT(handle,
324 smb_fname);
327 if (config->cwd == NULL) {
328 /* lstat before chdir. See note 1b above. */
329 return SMB_VFS_NEXT_LSTAT(handle,
330 smb_fname);
333 if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
334 /* POSIX sees symlinks. */
335 return SMB_VFS_NEXT_LSTAT(handle,
336 smb_fname);
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,
346 files_struct *fsp,
347 const struct vfs_open_how *_how)
349 struct vfs_open_how how = *_how;
350 struct widelinks_config *config = NULL;
351 int ret;
352 SMB_VFS_HANDLE_GET_DATA(handle,
353 config,
354 struct widelinks_config,
355 return -1);
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 ret = SMB_VFS_NEXT_OPENAT(handle,
369 dirfsp,
370 smb_fname,
371 fsp,
372 &how);
373 if (config->is_dfs_share && ret == -1 && errno == ENOENT) {
374 struct smb_filename *full_fname = NULL;
375 int lstat_ret;
377 full_fname = full_path_from_dirfsp_atname(talloc_tos(),
378 dirfsp,
379 smb_fname);
380 if (full_fname == NULL) {
381 errno = ENOMEM;
382 return -1;
384 lstat_ret = SMB_VFS_NEXT_LSTAT(handle,
385 full_fname);
386 if (lstat_ret == -1) {
388 * Path doesn't exist. We must
389 * return errno from LSTAT.
391 int saved_errno = errno;
392 TALLOC_FREE(full_fname);
393 errno = saved_errno;
394 return -1;
396 if (VALID_STAT(full_fname->st) &&
397 S_ISLNK(full_fname->st.st_ex_mode)) {
398 fsp->fsp_name->st = full_fname->st;
400 TALLOC_FREE(full_fname);
401 errno = ELOOP;
403 return ret;
406 static struct vfs_fn_pointers vfs_widelinks_fns = {
407 .connect_fn = widelinks_connect,
409 .openat_fn = widelinks_openat,
410 .lstat_fn = widelinks_lstat,
412 * NB. We don't need an lchown function as this
413 * is only called (a) on directory create and
414 * (b) on POSIX extensions names.
416 .chdir_fn = widelinks_chdir,
417 .getwd_fn = widelinks_getwd,
418 .realpath_fn = widelinks_realpath,
421 static_decl_vfs;
422 NTSTATUS vfs_widelinks_init(TALLOC_CTX *ctx)
424 return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
425 "widelinks",
426 &vfs_widelinks_fns);