ksmbd: reject delete-on-close for read-only files

DELETE_ON_CLOSE is currently accepted for files carrying the read-only
DOS attribute. The server consequently creates or opens the file and
marks it for deletion instead of returning STATUS_CANNOT_DELETE.

Reject creation of a new read-only file with DELETE_ON_CLOSE. For an
existing file, load the stored DOS attributes before accepting the
create option. Also reject FileDispositionInformation when the opened
file has the read-only attribute.

Preserve the explicit STATUS_CANNOT_DELETE value while unwinding the
CREATE request.

This fixes smb2.delete-on-close-perms.READONLY.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-07-05 15:43:29 +09:00
parent 5d47ebb279
commit 497dbc5999

View File

@ -3534,6 +3534,8 @@ int smb2_open(struct ksmbd_work *work)
file_present = true;
if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
struct xattr_dos_attrib da;
/*
* If file exists with under flags, return access
* denied error.
@ -3547,6 +3549,16 @@ int smb2_open(struct ksmbd_work *work)
if (!test_tree_conn_flag(tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
ksmbd_debug(SMB,
"User does not have write permission\n");
rc = -EACCES;
goto err_out;
}
if (test_share_config_flag(tcon->share_conf,
KSMBD_SHARE_FLAG_STORE_DOS_ATTRS) &&
ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path.mnt),
path.dentry, &da) > 0 &&
da.attr & FILE_ATTRIBUTE_READONLY) {
rsp->hdr.Status = STATUS_CANNOT_DELETE;
rc = -EACCES;
goto err_out;
}
@ -3564,6 +3576,13 @@ int smb2_open(struct ksmbd_work *work)
rc = 0;
}
if (!file_present && req->CreateOptions & FILE_DELETE_ON_CLOSE_LE &&
req->FileAttributes & FILE_ATTRIBUTE_READONLY_LE) {
rsp->hdr.Status = STATUS_CANNOT_DELETE;
rc = -EACCES;
goto err_out;
}
/*
* An explicit ::$DATA suffix names the unnamed data stream and is
* canonicalized to a NULL stream name (base file), but the request
@ -4204,7 +4223,8 @@ int smb2_open(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
else if (rc == -EOPNOTSUPP)
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
else if (rc == -EACCES || rc == -ESTALE || rc == -EXDEV) {
else if ((rc == -EACCES || rc == -ESTALE || rc == -EXDEV) &&
!rsp->hdr.Status) {
if (req->DesiredAccess & FILE_ACCESS_SYSTEM_SECURITY_LE)
rsp->hdr.Status = STATUS_PRIVILEGE_NOT_HELD;
else
@ -6953,6 +6973,9 @@ static int set_file_disposition_info(struct ksmbd_work *work,
return -EACCES;
}
if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_READONLY_LE)
return -EACCES;
inode = file_inode(fp->filp);
if (file_info->DeletePending) {
if (ksmbd_has_stream_without_delete_share(fp))
@ -7223,8 +7246,14 @@ int smb2_set_info(struct ksmbd_work *work)
return 0;
err_out:
if (rc == -EACCES || rc == -EPERM || rc == -EXDEV)
rsp->hdr.Status = STATUS_ACCESS_DENIED;
if (rc == -EACCES || rc == -EPERM || rc == -EXDEV) {
if (fp && req->InfoType == SMB2_O_INFO_FILE &&
req->FileInfoClass == FILE_DISPOSITION_INFORMATION &&
fp->f_ci->m_fattr & FILE_ATTRIBUTE_READONLY_LE)
rsp->hdr.Status = STATUS_CANNOT_DELETE;
else
rsp->hdr.Status = STATUS_ACCESS_DENIED;
}
else if (rc == -EINVAL)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
else if (rc == -EMSGSIZE)