Line data Source code
1 : /*
2 : * VFS module to disallow writes for older files
3 : *
4 : * Copyright (C) 2013, Volker Lendecke
5 : * Copyright (C) 2023-2024, Björn Jacke
6 : *
7 : * This program is free software; you can redistribute it and/or modify
8 : * it under the terms of the GNU General Public License as published by
9 : * the Free Software Foundation; either version 3 of the License, or
10 : * (at your option) any later version.
11 : *
12 : * This program is distributed in the hope that it will be useful,
13 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : * GNU General Public License for more details.
16 : *
17 : * You should have received a copy of the GNU General Public License
18 : * along with this program; if not, see <http://www.gnu.org/licenses/>.
19 : */
20 :
21 : #include "includes.h"
22 : #include "smbd/smbd.h"
23 : #include "system/filesys.h"
24 : #include "libcli/security/security.h"
25 :
26 : struct worm_config_data {
27 : double grace_period;
28 : };
29 : static const uint32_t write_access_flags = FILE_WRITE_DATA | FILE_APPEND_DATA |
30 : FILE_WRITE_ATTRIBUTES |
31 : DELETE_ACCESS | WRITE_DAC_ACCESS |
32 : WRITE_OWNER_ACCESS | FILE_WRITE_EA;
33 :
34 0 : static int vfs_worm_connect(struct vfs_handle_struct *handle,
35 : const char *service, const char *user)
36 : {
37 0 : struct worm_config_data *config = NULL;
38 : int ret;
39 :
40 0 : ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
41 0 : if (ret < 0) {
42 0 : return ret;
43 : }
44 :
45 0 : if (IS_IPC(handle->conn) || IS_PRINT(handle->conn)) {
46 0 : return 0;
47 : }
48 :
49 0 : config = talloc_zero(handle->conn, struct worm_config_data);
50 0 : if (config == NULL) {
51 0 : DBG_ERR("talloc_zero() failed\n");
52 0 : errno = ENOMEM;
53 0 : return -1;
54 : }
55 0 : config->grace_period = lp_parm_int(SNUM(handle->conn), "worm",
56 : "grace_period", 3600);
57 :
58 0 : SMB_VFS_HANDLE_SET_DATA(handle, config,
59 : NULL, struct worm_config_data,
60 : return -1);
61 0 : return 0;
62 :
63 : }
64 :
65 0 : static bool is_readonly(vfs_handle_struct *handle,
66 : const struct smb_filename *smb_fname)
67 : {
68 : double age;
69 0 : struct worm_config_data *config = NULL;
70 :
71 0 : SMB_VFS_HANDLE_GET_DATA(handle,
72 : config,
73 : struct worm_config_data,
74 : return true);
75 :
76 0 : if (!VALID_STAT(smb_fname->st)) {
77 0 : goto out;
78 : }
79 :
80 0 : age = timespec_elapsed(&smb_fname->st.st_ex_ctime);
81 :
82 0 : if (age > config->grace_period) {
83 0 : return true;
84 : }
85 :
86 0 : out:
87 0 : return false;
88 : }
89 0 : static bool fsp_is_readonly(vfs_handle_struct *handle, files_struct *fsp)
90 : {
91 : double age;
92 0 : struct worm_config_data *config = NULL;
93 :
94 0 : SMB_VFS_HANDLE_GET_DATA(handle,
95 : config,
96 : struct worm_config_data,
97 : return true);
98 :
99 0 : if (!VALID_STAT(fsp->fsp_name->st)) {
100 0 : goto out;
101 : }
102 :
103 0 : age = timespec_elapsed(&fsp->fsp_name->st.st_ex_ctime);
104 :
105 0 : if (age > config->grace_period) {
106 0 : return true;
107 : }
108 :
109 0 : out:
110 0 : return false;
111 : }
112 :
113 0 : static NTSTATUS vfs_worm_create_file(vfs_handle_struct *handle,
114 : struct smb_request *req,
115 : struct files_struct *dirfsp,
116 : struct smb_filename *smb_fname,
117 : uint32_t access_mask,
118 : uint32_t share_access,
119 : uint32_t create_disposition,
120 : uint32_t create_options,
121 : uint32_t file_attributes,
122 : uint32_t oplock_request,
123 : const struct smb2_lease *lease,
124 : uint64_t allocation_size,
125 : uint32_t private_flags,
126 : struct security_descriptor *sd,
127 : struct ea_list *ea_list,
128 : files_struct **result,
129 : int *pinfo,
130 : const struct smb2_create_blobs *in_context_blobs,
131 : struct smb2_create_blobs *out_context_blobs)
132 : {
133 : NTSTATUS status;
134 : bool readonly;
135 :
136 0 : readonly = is_readonly(handle, smb_fname);
137 :
138 0 : if (readonly && (access_mask & write_access_flags)) {
139 0 : return NT_STATUS_ACCESS_DENIED;
140 : }
141 :
142 0 : status = SMB_VFS_NEXT_CREATE_FILE(
143 : handle, req, dirfsp, smb_fname, access_mask,
144 : share_access, create_disposition, create_options,
145 : file_attributes, oplock_request, lease, allocation_size,
146 : private_flags, sd, ea_list, result, pinfo,
147 : in_context_blobs, out_context_blobs);
148 0 : if (!NT_STATUS_IS_OK(status)) {
149 0 : return status;
150 : }
151 :
152 : /*
153 : * Access via MAXIMUM_ALLOWED_ACCESS?
154 : */
155 0 : if (readonly && ((*result)->access_mask & write_access_flags)) {
156 0 : close_file_free(req, result, NORMAL_CLOSE);
157 0 : return NT_STATUS_ACCESS_DENIED;
158 : }
159 0 : return NT_STATUS_OK;
160 : }
161 :
162 0 : static int vfs_worm_openat(vfs_handle_struct *handle,
163 : const struct files_struct *dirfsp,
164 : const struct smb_filename *smb_fname,
165 : files_struct *fsp,
166 : const struct vfs_open_how *how)
167 : {
168 0 : if (is_readonly(handle, smb_fname) &&
169 0 : (fsp->access_mask & write_access_flags)) {
170 0 : errno = EACCES;
171 0 : return -1;
172 : }
173 :
174 0 : return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
175 : }
176 :
177 0 : static int vfs_worm_fntimes(vfs_handle_struct *handle,
178 : files_struct *fsp,
179 : struct smb_file_time *ft)
180 : {
181 0 : if (fsp_is_readonly(handle, fsp)) {
182 0 : errno = EACCES;
183 0 : return -1;
184 : }
185 :
186 0 : return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
187 : }
188 :
189 0 : static int vfs_worm_fchmod(vfs_handle_struct *handle,
190 : files_struct *fsp,
191 : mode_t mode)
192 : {
193 0 : if (fsp_is_readonly(handle, fsp)) {
194 0 : errno = EACCES;
195 0 : return -1;
196 : }
197 :
198 0 : return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
199 : }
200 :
201 0 : static int vfs_worm_fchown(vfs_handle_struct *handle,
202 : files_struct *fsp,
203 : uid_t uid,
204 : gid_t gid)
205 : {
206 0 : if (fsp_is_readonly(handle, fsp)) {
207 0 : errno = EACCES;
208 0 : return -1;
209 : }
210 :
211 0 : return SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid);
212 : }
213 :
214 0 : static int vfs_worm_renameat(vfs_handle_struct *handle,
215 : files_struct *srcfsp,
216 : const struct smb_filename *smb_fname_src,
217 : files_struct *dstfsp,
218 : const struct smb_filename *smb_fname_dst)
219 : {
220 : struct stat_ex dst_st;
221 : int ret;
222 :
223 0 : if (is_readonly(handle, smb_fname_src)) {
224 0 : errno = EACCES;
225 0 : return -1;
226 : }
227 :
228 : /* Check if destination is WORM-protected (fixes CVE-2026-2340) */
229 0 : ret = SMB_VFS_FSTATAT(handle->conn,
230 : dstfsp,
231 : smb_fname_dst,
232 : &dst_st,
233 : AT_SYMLINK_NOFOLLOW);
234 0 : if (ret == 0) {
235 0 : struct smb_filename dst_with_stat = *smb_fname_dst;
236 0 : dst_with_stat.st = dst_st;
237 0 : if (is_readonly(handle, &dst_with_stat)) {
238 0 : errno = EACCES;
239 0 : return -1;
240 : }
241 : }
242 :
243 0 : return SMB_VFS_NEXT_RENAMEAT(handle,
244 : srcfsp,
245 : smb_fname_src,
246 : dstfsp,
247 : smb_fname_dst);
248 : }
249 :
250 0 : static int vfs_worm_fsetxattr(struct vfs_handle_struct *handle,
251 : struct files_struct *fsp,
252 : const char *name,
253 : const void *value,
254 : size_t size,
255 : int flags)
256 : {
257 0 : if (fsp_is_readonly(handle, fsp)) {
258 0 : errno = EACCES;
259 0 : return -1;
260 : }
261 :
262 0 : return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags);
263 : }
264 :
265 0 : static int vfs_worm_fremotexattr(struct vfs_handle_struct *handle,
266 : struct files_struct *fsp,
267 : const char *name)
268 : {
269 0 : if (fsp_is_readonly(handle, fsp)) {
270 0 : errno = EACCES;
271 0 : return -1;
272 : }
273 :
274 0 : return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name);
275 : }
276 :
277 0 : static int vfs_worm_unlinkat(vfs_handle_struct *handle,
278 : struct files_struct *dirfsp,
279 : const struct smb_filename *smb_fname,
280 : int flags)
281 : {
282 0 : struct smb_filename *full_fname = NULL;
283 : bool readonly;
284 :
285 0 : full_fname = full_path_from_dirfsp_atname(talloc_tos(),
286 : dirfsp,
287 : smb_fname);
288 0 : if (full_fname == NULL) {
289 0 : return -1;
290 : }
291 :
292 0 : readonly = is_readonly(handle, full_fname);
293 :
294 0 : TALLOC_FREE(full_fname);
295 :
296 0 : if (readonly) {
297 0 : errno = EACCES;
298 0 : return -1;
299 : }
300 :
301 0 : return SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, smb_fname, flags);
302 : }
303 :
304 0 : static NTSTATUS vfs_worm_fset_dos_attributes(struct vfs_handle_struct *handle,
305 : struct files_struct *fsp,
306 : uint32_t dosmode)
307 : {
308 0 : if (fsp_is_readonly(handle, fsp)) {
309 0 : return NT_STATUS_ACCESS_DENIED;
310 : }
311 :
312 0 : return SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode);
313 : }
314 :
315 0 : static NTSTATUS vfs_worm_fset_nt_acl(vfs_handle_struct *handle,
316 : files_struct *fsp,
317 : uint32_t security_info_sent,
318 : const struct security_descriptor *psd)
319 : {
320 0 : if (fsp_is_readonly(handle, fsp)) {
321 0 : return NT_STATUS_ACCESS_DENIED;
322 : }
323 :
324 0 : return SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd);
325 : }
326 :
327 0 : static int vfs_worm_sys_acl_set_fd(vfs_handle_struct *handle,
328 : struct files_struct *fsp,
329 : SMB_ACL_TYPE_T type,
330 : SMB_ACL_T theacl)
331 : {
332 0 : if (fsp_is_readonly(handle, fsp)) {
333 0 : errno = EACCES;
334 0 : return -1;
335 : }
336 :
337 0 : return SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl);
338 : }
339 :
340 0 : static int vfs_worm_sys_acl_delete_def_fd(vfs_handle_struct *handle,
341 : struct files_struct *fsp)
342 : {
343 0 : if (fsp_is_readonly(handle, fsp)) {
344 0 : errno = EACCES;
345 0 : return -1;
346 : }
347 :
348 0 : return SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp);
349 : }
350 :
351 : static struct vfs_fn_pointers vfs_worm_fns = {
352 : .connect_fn = vfs_worm_connect,
353 : .create_file_fn = vfs_worm_create_file,
354 : .openat_fn = vfs_worm_openat,
355 : .fntimes_fn = vfs_worm_fntimes,
356 : .fchmod_fn = vfs_worm_fchmod,
357 : .fchown_fn = vfs_worm_fchown,
358 : .renameat_fn = vfs_worm_renameat,
359 : .fsetxattr_fn = vfs_worm_fsetxattr,
360 : .fremovexattr_fn = vfs_worm_fremotexattr,
361 : .unlinkat_fn = vfs_worm_unlinkat,
362 : .fset_dos_attributes_fn = vfs_worm_fset_dos_attributes,
363 : .fset_nt_acl_fn = vfs_worm_fset_nt_acl,
364 : .sys_acl_set_fd_fn = vfs_worm_sys_acl_set_fd,
365 : .sys_acl_delete_def_fd_fn = vfs_worm_sys_acl_delete_def_fd,
366 : };
367 :
368 : static_decl_vfs;
369 26 : NTSTATUS vfs_worm_init(TALLOC_CTX *ctx)
370 : {
371 : NTSTATUS ret;
372 :
373 26 : ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "worm",
374 : &vfs_worm_fns);
375 26 : if (!NT_STATUS_IS_OK(ret)) {
376 0 : return ret;
377 : }
378 :
379 26 : return ret;
380 : }
|