![Steve French](/assets/img/avatar_default.png)
In order to debug certain problems it is important to be able to decrypt network traces (e.g. wireshark) but to do this we need to be able to dump out the encryption/decryption keys. Dumping them to an ioctl is safer than dumping then to dmesg, (and better than showing all keys in a pseudofile). Restrict this to root (CAP_SYS_ADMIN), and only for a mount that this admin has access to. Sample smbinfo output: SMB3.0 encryption Session Id: 0x82d2ec52 Session Key: a5 6d 81 d0 e c1 ca e1 d8 13 aa 20 e8 f2 cc 71 Server Encryption Key: 1a c3 be ba 3d fc dc 3c e bc 93 9e 50 9e 19 c1 Server Decryption Key: e0 d4 d9 43 1b a2 1b e3 d8 76 77 49 56 f7 20 88 Reviewed-by: Aurelien Aptel <aaptel@suse.com> Pavel Shilovsky <pshilov@microsoft.com> Signed-off-by: Steve French <stfrench@microsoft.com>
310 lines
8.2 KiB
C
310 lines
8.2 KiB
C
/*
|
|
* fs/cifs/ioctl.c
|
|
*
|
|
* vfs operations that deal with io control
|
|
*
|
|
* Copyright (C) International Business Machines Corp., 2005,2013
|
|
* Author(s): Steve French (sfrench@us.ibm.com)
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
|
* the GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/file.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/pagemap.h>
|
|
#include "cifspdu.h"
|
|
#include "cifsglob.h"
|
|
#include "cifsproto.h"
|
|
#include "cifs_debug.h"
|
|
#include "cifsfs.h"
|
|
#include "cifs_ioctl.h"
|
|
#include "smb2proto.h"
|
|
#include <linux/btrfs.h>
|
|
|
|
static long cifs_ioctl_query_info(unsigned int xid, struct file *filep,
|
|
unsigned long p)
|
|
{
|
|
struct inode *inode = file_inode(filep);
|
|
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
|
|
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
|
|
struct dentry *dentry = filep->f_path.dentry;
|
|
unsigned char *path;
|
|
__le16 *utf16_path = NULL, root_path;
|
|
int rc = 0;
|
|
|
|
path = build_path_from_dentry(dentry);
|
|
if (path == NULL)
|
|
return -ENOMEM;
|
|
|
|
cifs_dbg(FYI, "%s %s\n", __func__, path);
|
|
|
|
if (!path[0]) {
|
|
root_path = 0;
|
|
utf16_path = &root_path;
|
|
} else {
|
|
utf16_path = cifs_convert_path_to_utf16(path + 1, cifs_sb);
|
|
if (!utf16_path) {
|
|
rc = -ENOMEM;
|
|
goto ici_exit;
|
|
}
|
|
}
|
|
|
|
if (tcon->ses->server->ops->ioctl_query_info)
|
|
rc = tcon->ses->server->ops->ioctl_query_info(
|
|
xid, tcon, utf16_path,
|
|
filep->private_data ? 0 : 1, p);
|
|
else
|
|
rc = -EOPNOTSUPP;
|
|
|
|
ici_exit:
|
|
if (utf16_path != &root_path)
|
|
kfree(utf16_path);
|
|
kfree(path);
|
|
return rc;
|
|
}
|
|
|
|
static long cifs_ioctl_copychunk(unsigned int xid, struct file *dst_file,
|
|
unsigned long srcfd)
|
|
{
|
|
int rc;
|
|
struct fd src_file;
|
|
struct inode *src_inode;
|
|
|
|
cifs_dbg(FYI, "ioctl copychunk range\n");
|
|
/* the destination must be opened for writing */
|
|
if (!(dst_file->f_mode & FMODE_WRITE)) {
|
|
cifs_dbg(FYI, "file target not open for write\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check if target volume is readonly and take reference */
|
|
rc = mnt_want_write_file(dst_file);
|
|
if (rc) {
|
|
cifs_dbg(FYI, "mnt_want_write failed with rc %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
src_file = fdget(srcfd);
|
|
if (!src_file.file) {
|
|
rc = -EBADF;
|
|
goto out_drop_write;
|
|
}
|
|
|
|
if (src_file.file->f_op->unlocked_ioctl != cifs_ioctl) {
|
|
rc = -EBADF;
|
|
cifs_dbg(VFS, "src file seems to be from a different filesystem type\n");
|
|
goto out_fput;
|
|
}
|
|
|
|
src_inode = file_inode(src_file.file);
|
|
rc = -EINVAL;
|
|
if (S_ISDIR(src_inode->i_mode))
|
|
goto out_fput;
|
|
|
|
rc = cifs_file_copychunk_range(xid, src_file.file, 0, dst_file, 0,
|
|
src_inode->i_size, 0);
|
|
if (rc > 0)
|
|
rc = 0;
|
|
out_fput:
|
|
fdput(src_file);
|
|
out_drop_write:
|
|
mnt_drop_write_file(dst_file);
|
|
return rc;
|
|
}
|
|
|
|
static long smb_mnt_get_fsinfo(unsigned int xid, struct cifs_tcon *tcon,
|
|
void __user *arg)
|
|
{
|
|
int rc = 0;
|
|
struct smb_mnt_fs_info *fsinf;
|
|
|
|
fsinf = kzalloc(sizeof(struct smb_mnt_fs_info), GFP_KERNEL);
|
|
if (fsinf == NULL)
|
|
return -ENOMEM;
|
|
|
|
fsinf->version = 1;
|
|
fsinf->protocol_id = tcon->ses->server->vals->protocol_id;
|
|
fsinf->device_characteristics =
|
|
le32_to_cpu(tcon->fsDevInfo.DeviceCharacteristics);
|
|
fsinf->device_type = le32_to_cpu(tcon->fsDevInfo.DeviceType);
|
|
fsinf->fs_attributes = le32_to_cpu(tcon->fsAttrInfo.Attributes);
|
|
fsinf->max_path_component =
|
|
le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength);
|
|
fsinf->vol_serial_number = tcon->vol_serial_number;
|
|
fsinf->vol_create_time = le64_to_cpu(tcon->vol_create_time);
|
|
fsinf->share_flags = tcon->share_flags;
|
|
fsinf->share_caps = le32_to_cpu(tcon->capabilities);
|
|
fsinf->sector_flags = tcon->ss_flags;
|
|
fsinf->optimal_sector_size = tcon->perf_sector_size;
|
|
fsinf->max_bytes_chunk = tcon->max_bytes_chunk;
|
|
fsinf->maximal_access = tcon->maximal_access;
|
|
fsinf->cifs_posix_caps = le64_to_cpu(tcon->fsUnixInfo.Capability);
|
|
|
|
if (copy_to_user(arg, fsinf, sizeof(struct smb_mnt_fs_info)))
|
|
rc = -EFAULT;
|
|
|
|
kfree(fsinf);
|
|
return rc;
|
|
}
|
|
|
|
long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
|
|
{
|
|
struct inode *inode = file_inode(filep);
|
|
struct smb3_key_debug_info pkey_inf;
|
|
int rc = -ENOTTY; /* strange error - but the precedent */
|
|
unsigned int xid;
|
|
struct cifsFileInfo *pSMBFile = filep->private_data;
|
|
struct cifs_tcon *tcon;
|
|
__u64 ExtAttrBits = 0;
|
|
__u64 caps;
|
|
|
|
xid = get_xid();
|
|
|
|
cifs_dbg(FYI, "cifs ioctl 0x%x\n", command);
|
|
switch (command) {
|
|
case FS_IOC_GETFLAGS:
|
|
if (pSMBFile == NULL)
|
|
break;
|
|
tcon = tlink_tcon(pSMBFile->tlink);
|
|
caps = le64_to_cpu(tcon->fsUnixInfo.Capability);
|
|
#ifdef CONFIG_CIFS_POSIX
|
|
if (CIFS_UNIX_EXTATTR_CAP & caps) {
|
|
__u64 ExtAttrMask = 0;
|
|
rc = CIFSGetExtAttr(xid, tcon,
|
|
pSMBFile->fid.netfid,
|
|
&ExtAttrBits, &ExtAttrMask);
|
|
if (rc == 0)
|
|
rc = put_user(ExtAttrBits &
|
|
FS_FL_USER_VISIBLE,
|
|
(int __user *)arg);
|
|
if (rc != EOPNOTSUPP)
|
|
break;
|
|
}
|
|
#endif /* CONFIG_CIFS_POSIX */
|
|
rc = 0;
|
|
if (CIFS_I(inode)->cifsAttrs & ATTR_COMPRESSED) {
|
|
/* add in the compressed bit */
|
|
ExtAttrBits = FS_COMPR_FL;
|
|
rc = put_user(ExtAttrBits & FS_FL_USER_VISIBLE,
|
|
(int __user *)arg);
|
|
}
|
|
break;
|
|
case FS_IOC_SETFLAGS:
|
|
if (pSMBFile == NULL)
|
|
break;
|
|
tcon = tlink_tcon(pSMBFile->tlink);
|
|
caps = le64_to_cpu(tcon->fsUnixInfo.Capability);
|
|
|
|
if (get_user(ExtAttrBits, (int __user *)arg)) {
|
|
rc = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* if (CIFS_UNIX_EXTATTR_CAP & caps)
|
|
* rc = CIFSSetExtAttr(xid, tcon,
|
|
* pSMBFile->fid.netfid,
|
|
* extAttrBits,
|
|
* &ExtAttrMask);
|
|
* if (rc != EOPNOTSUPP)
|
|
* break;
|
|
*/
|
|
|
|
/* Currently only flag we can set is compressed flag */
|
|
if ((ExtAttrBits & FS_COMPR_FL) == 0)
|
|
break;
|
|
|
|
/* Try to set compress flag */
|
|
if (tcon->ses->server->ops->set_compression) {
|
|
rc = tcon->ses->server->ops->set_compression(
|
|
xid, tcon, pSMBFile);
|
|
cifs_dbg(FYI, "set compress flag rc %d\n", rc);
|
|
}
|
|
break;
|
|
case CIFS_IOC_COPYCHUNK_FILE:
|
|
rc = cifs_ioctl_copychunk(xid, filep, arg);
|
|
break;
|
|
case CIFS_QUERY_INFO:
|
|
rc = cifs_ioctl_query_info(xid, filep, arg);
|
|
break;
|
|
case CIFS_IOC_SET_INTEGRITY:
|
|
if (pSMBFile == NULL)
|
|
break;
|
|
tcon = tlink_tcon(pSMBFile->tlink);
|
|
if (tcon->ses->server->ops->set_integrity)
|
|
rc = tcon->ses->server->ops->set_integrity(xid,
|
|
tcon, pSMBFile);
|
|
else
|
|
rc = -EOPNOTSUPP;
|
|
break;
|
|
case CIFS_IOC_GET_MNT_INFO:
|
|
if (pSMBFile == NULL)
|
|
break;
|
|
tcon = tlink_tcon(pSMBFile->tlink);
|
|
rc = smb_mnt_get_fsinfo(xid, tcon, (void __user *)arg);
|
|
break;
|
|
case CIFS_ENUMERATE_SNAPSHOTS:
|
|
if (pSMBFile == NULL)
|
|
break;
|
|
if (arg == 0) {
|
|
rc = -EINVAL;
|
|
goto cifs_ioc_exit;
|
|
}
|
|
tcon = tlink_tcon(pSMBFile->tlink);
|
|
if (tcon->ses->server->ops->enum_snapshots)
|
|
rc = tcon->ses->server->ops->enum_snapshots(xid, tcon,
|
|
pSMBFile, (void __user *)arg);
|
|
else
|
|
rc = -EOPNOTSUPP;
|
|
break;
|
|
case CIFS_DUMP_KEY:
|
|
if (pSMBFile == NULL)
|
|
break;
|
|
if (!capable(CAP_SYS_ADMIN)) {
|
|
rc = -EACCES;
|
|
break;
|
|
}
|
|
|
|
tcon = tlink_tcon(pSMBFile->tlink);
|
|
if (!smb3_encryption_required(tcon)) {
|
|
rc = -EOPNOTSUPP;
|
|
break;
|
|
}
|
|
pkey_inf.cipher_type =
|
|
le16_to_cpu(tcon->ses->server->cipher_type);
|
|
pkey_inf.Suid = tcon->ses->Suid;
|
|
memcpy(pkey_inf.auth_key, tcon->ses->auth_key.response,
|
|
16 /* SMB2_NTLMV2_SESSKEY_SIZE */);
|
|
memcpy(pkey_inf.smb3decryptionkey,
|
|
tcon->ses->smb3decryptionkey, SMB3_SIGN_KEY_SIZE);
|
|
memcpy(pkey_inf.smb3encryptionkey,
|
|
tcon->ses->smb3encryptionkey, SMB3_SIGN_KEY_SIZE);
|
|
if (copy_to_user((void __user *)arg, &pkey_inf,
|
|
sizeof(struct smb3_key_debug_info)))
|
|
rc = -EFAULT;
|
|
else
|
|
rc = 0;
|
|
break;
|
|
default:
|
|
cifs_dbg(FYI, "unsupported ioctl\n");
|
|
break;
|
|
}
|
|
cifs_ioc_exit:
|
|
free_xid(xid);
|
|
return rc;
|
|
}
|