ksmbd: support access-based directory enumeration

SMB shares can advertise access-based directory enumeration. ksmbd does
not currently provide a share option or filter inaccessible directory
entries.

Add a hide-unreadable share flag and advertise
SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM when it is enabled. During
QUERY_DIRECTORY, omit entries unless the connected user has
FILE_READ_DATA, FILE_READ_EA, and FILE_READ_ATTRIBUTES access according
to the Windows ACL.

Keep the existing implicit access allowances for normal CREATE
permission checks while using strict access-mask matching for directory
enumeration.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-07-05 15:20:06 +09:00
parent cc2f133e80
commit e5f42cb757
4 changed files with 36 additions and 5 deletions

View File

@ -377,6 +377,7 @@ enum KSMBD_TREE_CONN_STATUS {
#define KSMBD_SHARE_FLAG_UPDATE BIT(14)
#define KSMBD_SHARE_FLAG_CROSSMNT BIT(15)
#define KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY BIT(16)
#define KSMBD_SHARE_FLAG_HIDE_UNREADABLE BIT(17)
/*
* Tree connect request flags.

View File

@ -2346,6 +2346,10 @@ int smb2_tree_connect(struct ksmbd_work *work)
if (conn->dialect == SMB311_PROT_ID &&
conn->compress_algorithm != SMB3_COMPRESS_NONE)
rsp->ShareFlags |= cpu_to_le32(SMB2_SHAREFLAG_COMPRESS_DATA);
if (share && test_share_config_flag(share,
KSMBD_SHARE_FLAG_HIDE_UNREADABLE))
rsp->ShareFlags |=
cpu_to_le32(SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM);
rc = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_tree_connect_rsp));
if (rc)
@ -3620,7 +3624,7 @@ int smb2_open(struct ksmbd_work *work)
if (file_present && !(req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
rc = smb_check_perm_dacl(conn, &path, &daccess,
req->DesiredAccess,
sess->user->uid);
sess->user->uid, false);
if (rc)
goto err_out;
}
@ -4595,6 +4599,7 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
for (i = 0; i < priv->d_info->num_entry; i++) {
struct dentry *dent;
struct path path;
if (dentry_name(priv->d_info, priv->info_level))
return -EINVAL;
@ -4617,6 +4622,23 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
continue;
}
if (test_share_config_flag(priv->work->tcon->share_conf,
KSMBD_SHARE_FLAG_HIDE_UNREADABLE)) {
__le32 daccess = FILE_READ_DATA_LE | FILE_READ_EA_LE |
FILE_READ_ATTRIBUTES_LE;
path.mnt = priv->dir_fp->filp->f_path.mnt;
path.dentry = dent;
rc = smb_check_perm_dacl(priv->work->conn, &path,
&daccess, daccess,
priv->work->sess->user->uid,
true);
if (rc) {
dput(dent);
continue;
}
}
ksmbd_kstat.kstat = &kstat;
if (priv->info_level != FILE_NAMES_INFORMATION) {
rc = ksmbd_vfs_fill_dentry_attrs(priv->work,

View File

@ -1432,7 +1432,8 @@ bool smb_inherit_flags(int flags, bool is_dir)
}
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
__le32 *pdaccess, __le32 raw_daccess, int uid)
__le32 *pdaccess, __le32 raw_daccess, int uid,
bool strict)
{
struct mnt_idmap *idmap = mnt_idmap(path->mnt);
struct smb_ntsd *pntsd = NULL;
@ -1617,8 +1618,14 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
}
check_access_bits:
if (granted &
~(access_bits | FILE_READ_ATTRIBUTES | READ_CONTROL | WRITE_DAC | DELETE)) {
if (strict) {
access_bits &= granted;
} else {
access_bits |= FILE_READ_ATTRIBUTES | READ_CONTROL |
WRITE_DAC | DELETE;
}
if (granted & ~access_bits) {
ksmbd_debug(SMB, "Access denied with winACL, granted : %x, access_req : %x\n",
granted, le32_to_cpu(ace->access_req));
rc = -EACCES;

View File

@ -95,7 +95,8 @@ bool smb_inherit_flags(int flags, bool is_dir);
int smb_inherit_dacl(struct ksmbd_conn *conn, const struct path *path,
unsigned int uid, unsigned int gid);
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
__le32 *pdaccess, __le32 raw_daccess, int uid);
__le32 *pdaccess, __le32 raw_daccess, int uid,
bool strict);
int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
const struct path *path, struct smb_ntsd *pntsd, int ntsd_len,
bool type_check, bool get_write);