mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-02 16:44:10 +08:00
a34dc4a9b9
When user switch samba to ksmbd, The following message flood is coming
when accessing files. Samba seems to changs dos attribute version to v5.
This patch downgrade ndr version error message to debug.
$ dmesg
...
[68971.766914] ksmbd: v5 version is not supported
[68971.779808] ksmbd: v5 version is not supported
[68971.871544] ksmbd: v5 version is not supported
[68971.910135] ksmbd: v5 version is not supported
...
Cc: stable@vger.kernel.org
Fixes: e2f34481b2
("cifsd: add server-side procedures for SMB3")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
515 lines
9.8 KiB
C
515 lines
9.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2021 Samsung Electronics Co., Ltd.
|
|
* Author(s): Namjae Jeon <linkinjeon@kernel.org>
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include "glob.h"
|
|
#include "ndr.h"
|
|
|
|
static inline char *ndr_get_field(struct ndr *n)
|
|
{
|
|
return n->data + n->offset;
|
|
}
|
|
|
|
static int try_to_realloc_ndr_blob(struct ndr *n, size_t sz)
|
|
{
|
|
char *data;
|
|
|
|
data = krealloc(n->data, n->offset + sz + 1024, GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
n->data = data;
|
|
n->length += 1024;
|
|
memset(n->data + n->offset, 0, 1024);
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_write_int16(struct ndr *n, __u16 value)
|
|
{
|
|
if (n->length <= n->offset + sizeof(value)) {
|
|
int ret;
|
|
|
|
ret = try_to_realloc_ndr_blob(n, sizeof(value));
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
*(__le16 *)ndr_get_field(n) = cpu_to_le16(value);
|
|
n->offset += sizeof(value);
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_write_int32(struct ndr *n, __u32 value)
|
|
{
|
|
if (n->length <= n->offset + sizeof(value)) {
|
|
int ret;
|
|
|
|
ret = try_to_realloc_ndr_blob(n, sizeof(value));
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
*(__le32 *)ndr_get_field(n) = cpu_to_le32(value);
|
|
n->offset += sizeof(value);
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_write_int64(struct ndr *n, __u64 value)
|
|
{
|
|
if (n->length <= n->offset + sizeof(value)) {
|
|
int ret;
|
|
|
|
ret = try_to_realloc_ndr_blob(n, sizeof(value));
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
*(__le64 *)ndr_get_field(n) = cpu_to_le64(value);
|
|
n->offset += sizeof(value);
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_write_bytes(struct ndr *n, void *value, size_t sz)
|
|
{
|
|
if (n->length <= n->offset + sz) {
|
|
int ret;
|
|
|
|
ret = try_to_realloc_ndr_blob(n, sz);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
memcpy(ndr_get_field(n), value, sz);
|
|
n->offset += sz;
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_write_string(struct ndr *n, char *value)
|
|
{
|
|
size_t sz;
|
|
|
|
sz = strlen(value) + 1;
|
|
if (n->length <= n->offset + sz) {
|
|
int ret;
|
|
|
|
ret = try_to_realloc_ndr_blob(n, sz);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
memcpy(ndr_get_field(n), value, sz);
|
|
n->offset += sz;
|
|
n->offset = ALIGN(n->offset, 2);
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_read_string(struct ndr *n, void *value, size_t sz)
|
|
{
|
|
int len;
|
|
|
|
if (n->offset + sz > n->length)
|
|
return -EINVAL;
|
|
|
|
len = strnlen(ndr_get_field(n), sz);
|
|
if (value)
|
|
memcpy(value, ndr_get_field(n), len);
|
|
len++;
|
|
n->offset += len;
|
|
n->offset = ALIGN(n->offset, 2);
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_read_bytes(struct ndr *n, void *value, size_t sz)
|
|
{
|
|
if (n->offset + sz > n->length)
|
|
return -EINVAL;
|
|
|
|
if (value)
|
|
memcpy(value, ndr_get_field(n), sz);
|
|
n->offset += sz;
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_read_int16(struct ndr *n, __u16 *value)
|
|
{
|
|
if (n->offset + sizeof(__u16) > n->length)
|
|
return -EINVAL;
|
|
|
|
if (value)
|
|
*value = le16_to_cpu(*(__le16 *)ndr_get_field(n));
|
|
n->offset += sizeof(__u16);
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_read_int32(struct ndr *n, __u32 *value)
|
|
{
|
|
if (n->offset + sizeof(__u32) > n->length)
|
|
return -EINVAL;
|
|
|
|
if (value)
|
|
*value = le32_to_cpu(*(__le32 *)ndr_get_field(n));
|
|
n->offset += sizeof(__u32);
|
|
return 0;
|
|
}
|
|
|
|
static int ndr_read_int64(struct ndr *n, __u64 *value)
|
|
{
|
|
if (n->offset + sizeof(__u64) > n->length)
|
|
return -EINVAL;
|
|
|
|
if (value)
|
|
*value = le64_to_cpu(*(__le64 *)ndr_get_field(n));
|
|
n->offset += sizeof(__u64);
|
|
return 0;
|
|
}
|
|
|
|
int ndr_encode_dos_attr(struct ndr *n, struct xattr_dos_attrib *da)
|
|
{
|
|
char hex_attr[12] = {0};
|
|
int ret;
|
|
|
|
n->offset = 0;
|
|
n->length = 1024;
|
|
n->data = kzalloc(n->length, GFP_KERNEL);
|
|
if (!n->data)
|
|
return -ENOMEM;
|
|
|
|
if (da->version == 3) {
|
|
snprintf(hex_attr, 10, "0x%x", da->attr);
|
|
ret = ndr_write_string(n, hex_attr);
|
|
} else {
|
|
ret = ndr_write_string(n, "");
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int16(n, da->version);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int32(n, da->version);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int32(n, da->flags);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int32(n, da->attr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (da->version == 3) {
|
|
ret = ndr_write_int32(n, da->ea_size);
|
|
if (ret)
|
|
return ret;
|
|
ret = ndr_write_int64(n, da->size);
|
|
if (ret)
|
|
return ret;
|
|
ret = ndr_write_int64(n, da->alloc_size);
|
|
} else {
|
|
ret = ndr_write_int64(n, da->itime);
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int64(n, da->create_time);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (da->version == 3)
|
|
ret = ndr_write_int64(n, da->change_time);
|
|
return ret;
|
|
}
|
|
|
|
int ndr_decode_dos_attr(struct ndr *n, struct xattr_dos_attrib *da)
|
|
{
|
|
char hex_attr[12];
|
|
unsigned int version2;
|
|
int ret;
|
|
|
|
n->offset = 0;
|
|
ret = ndr_read_string(n, hex_attr, sizeof(hex_attr));
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_read_int16(n, &da->version);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (da->version != 3 && da->version != 4) {
|
|
ksmbd_debug(VFS, "v%d version is not supported\n", da->version);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = ndr_read_int32(n, &version2);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (da->version != version2) {
|
|
ksmbd_debug(VFS, "ndr version mismatched(version: %d, version2: %d)\n",
|
|
da->version, version2);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = ndr_read_int32(n, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_read_int32(n, &da->attr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (da->version == 4) {
|
|
ret = ndr_read_int64(n, &da->itime);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_read_int64(n, &da->create_time);
|
|
} else {
|
|
ret = ndr_read_int32(n, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_read_int64(n, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_read_int64(n, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_read_int64(n, &da->create_time);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_read_int64(n, NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ndr_encode_posix_acl_entry(struct ndr *n, struct xattr_smb_acl *acl)
|
|
{
|
|
int i, ret;
|
|
|
|
ret = ndr_write_int32(n, acl->count);
|
|
if (ret)
|
|
return ret;
|
|
|
|
n->offset = ALIGN(n->offset, 8);
|
|
ret = ndr_write_int32(n, acl->count);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int32(n, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < acl->count; i++) {
|
|
n->offset = ALIGN(n->offset, 8);
|
|
ret = ndr_write_int16(n, acl->entries[i].type);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int16(n, acl->entries[i].type);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (acl->entries[i].type == SMB_ACL_USER) {
|
|
n->offset = ALIGN(n->offset, 8);
|
|
ret = ndr_write_int64(n, acl->entries[i].uid);
|
|
} else if (acl->entries[i].type == SMB_ACL_GROUP) {
|
|
n->offset = ALIGN(n->offset, 8);
|
|
ret = ndr_write_int64(n, acl->entries[i].gid);
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* push permission */
|
|
ret = ndr_write_int32(n, acl->entries[i].perm);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ndr_encode_posix_acl(struct ndr *n,
|
|
struct user_namespace *user_ns,
|
|
struct inode *inode,
|
|
struct xattr_smb_acl *acl,
|
|
struct xattr_smb_acl *def_acl)
|
|
{
|
|
unsigned int ref_id = 0x00020000;
|
|
int ret;
|
|
vfsuid_t vfsuid;
|
|
vfsgid_t vfsgid;
|
|
|
|
n->offset = 0;
|
|
n->length = 1024;
|
|
n->data = kzalloc(n->length, GFP_KERNEL);
|
|
if (!n->data)
|
|
return -ENOMEM;
|
|
|
|
if (acl) {
|
|
/* ACL ACCESS */
|
|
ret = ndr_write_int32(n, ref_id);
|
|
ref_id += 4;
|
|
} else {
|
|
ret = ndr_write_int32(n, 0);
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (def_acl) {
|
|
/* DEFAULT ACL ACCESS */
|
|
ret = ndr_write_int32(n, ref_id);
|
|
ref_id += 4;
|
|
} else {
|
|
ret = ndr_write_int32(n, 0);
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
|
|
vfsuid = i_uid_into_vfsuid(user_ns, inode);
|
|
ret = ndr_write_int64(n, from_kuid(&init_user_ns, vfsuid_into_kuid(vfsuid)));
|
|
if (ret)
|
|
return ret;
|
|
vfsgid = i_gid_into_vfsgid(user_ns, inode);
|
|
ret = ndr_write_int64(n, from_kgid(&init_user_ns, vfsgid_into_kgid(vfsgid)));
|
|
if (ret)
|
|
return ret;
|
|
ret = ndr_write_int32(n, inode->i_mode);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (acl) {
|
|
ret = ndr_encode_posix_acl_entry(n, acl);
|
|
if (def_acl && !ret)
|
|
ret = ndr_encode_posix_acl_entry(n, def_acl);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int ndr_encode_v4_ntacl(struct ndr *n, struct xattr_ntacl *acl)
|
|
{
|
|
unsigned int ref_id = 0x00020004;
|
|
int ret;
|
|
|
|
n->offset = 0;
|
|
n->length = 2048;
|
|
n->data = kzalloc(n->length, GFP_KERNEL);
|
|
if (!n->data)
|
|
return -ENOMEM;
|
|
|
|
ret = ndr_write_int16(n, acl->version);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int32(n, acl->version);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int16(n, 2);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int32(n, ref_id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* push hash type and hash 64bytes */
|
|
ret = ndr_write_int16(n, acl->hash_type);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_bytes(n, acl->hash, XATTR_SD_HASH_SIZE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_bytes(n, acl->desc, acl->desc_len);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_int64(n, acl->current_time);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_write_bytes(n, acl->posix_acl_hash, XATTR_SD_HASH_SIZE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* push ndr for security descriptor */
|
|
ret = ndr_write_bytes(n, acl->sd_buf, acl->sd_size);
|
|
return ret;
|
|
}
|
|
|
|
int ndr_decode_v4_ntacl(struct ndr *n, struct xattr_ntacl *acl)
|
|
{
|
|
unsigned int version2;
|
|
int ret;
|
|
|
|
n->offset = 0;
|
|
ret = ndr_read_int16(n, &acl->version);
|
|
if (ret)
|
|
return ret;
|
|
if (acl->version != 4) {
|
|
ksmbd_debug(VFS, "v%d version is not supported\n", acl->version);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = ndr_read_int32(n, &version2);
|
|
if (ret)
|
|
return ret;
|
|
if (acl->version != version2) {
|
|
ksmbd_debug(VFS, "ndr version mismatched(version: %d, version2: %d)\n",
|
|
acl->version, version2);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Read Level */
|
|
ret = ndr_read_int16(n, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Read Ref Id */
|
|
ret = ndr_read_int32(n, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_read_int16(n, &acl->hash_type);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ndr_read_bytes(n, acl->hash, XATTR_SD_HASH_SIZE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ndr_read_bytes(n, acl->desc, 10);
|
|
if (strncmp(acl->desc, "posix_acl", 9)) {
|
|
pr_err("Invalid acl description : %s\n", acl->desc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Read Time */
|
|
ret = ndr_read_int64(n, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Read Posix ACL hash */
|
|
ret = ndr_read_bytes(n, acl->posix_acl_hash, XATTR_SD_HASH_SIZE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
acl->sd_size = n->length - n->offset;
|
|
acl->sd_buf = kzalloc(acl->sd_size, GFP_KERNEL);
|
|
if (!acl->sd_buf)
|
|
return -ENOMEM;
|
|
|
|
ret = ndr_read_bytes(n, acl->sd_buf, acl->sd_size);
|
|
return ret;
|
|
}
|