linux/fs/cifs/ioctl.c
<<
>>
Prefs
   1// SPDX-License-Identifier: LGPL-2.1
   2/*
   3 *   fs/cifs/ioctl.c
   4 *
   5 *   vfs operations that deal with io control
   6 *
   7 *   Copyright (C) International Business Machines  Corp., 2005,2013
   8 *   Author(s): Steve French (sfrench@us.ibm.com)
   9 *
  10 */
  11
  12#include <linux/fs.h>
  13#include <linux/file.h>
  14#include <linux/mount.h>
  15#include <linux/mm.h>
  16#include <linux/pagemap.h>
  17#include "cifspdu.h"
  18#include "cifsglob.h"
  19#include "cifsproto.h"
  20#include "cifs_debug.h"
  21#include "cifsfs.h"
  22#include "cifs_ioctl.h"
  23#include "smb2proto.h"
  24#include "smb2glob.h"
  25#include <linux/btrfs.h>
  26
  27static long cifs_ioctl_query_info(unsigned int xid, struct file *filep,
  28                                  unsigned long p)
  29{
  30        struct inode *inode = file_inode(filep);
  31        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
  32        struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
  33        struct dentry *dentry = filep->f_path.dentry;
  34        const unsigned char *path;
  35        void *page = alloc_dentry_path();
  36        __le16 *utf16_path = NULL, root_path;
  37        int rc = 0;
  38
  39        path = build_path_from_dentry(dentry, page);
  40        if (IS_ERR(path)) {
  41                free_dentry_path(page);
  42                return PTR_ERR(path);
  43        }
  44
  45        cifs_dbg(FYI, "%s %s\n", __func__, path);
  46
  47        if (!path[0]) {
  48                root_path = 0;
  49                utf16_path = &root_path;
  50        } else {
  51                utf16_path = cifs_convert_path_to_utf16(path + 1, cifs_sb);
  52                if (!utf16_path) {
  53                        rc = -ENOMEM;
  54                        goto ici_exit;
  55                }
  56        }
  57
  58        if (tcon->ses->server->ops->ioctl_query_info)
  59                rc = tcon->ses->server->ops->ioctl_query_info(
  60                                xid, tcon, cifs_sb, utf16_path,
  61                                filep->private_data ? 0 : 1, p);
  62        else
  63                rc = -EOPNOTSUPP;
  64
  65 ici_exit:
  66        if (utf16_path != &root_path)
  67                kfree(utf16_path);
  68        free_dentry_path(page);
  69        return rc;
  70}
  71
  72static long cifs_ioctl_copychunk(unsigned int xid, struct file *dst_file,
  73                        unsigned long srcfd)
  74{
  75        int rc;
  76        struct fd src_file;
  77        struct inode *src_inode;
  78
  79        cifs_dbg(FYI, "ioctl copychunk range\n");
  80        /* the destination must be opened for writing */
  81        if (!(dst_file->f_mode & FMODE_WRITE)) {
  82                cifs_dbg(FYI, "file target not open for write\n");
  83                return -EINVAL;
  84        }
  85
  86        /* check if target volume is readonly and take reference */
  87        rc = mnt_want_write_file(dst_file);
  88        if (rc) {
  89                cifs_dbg(FYI, "mnt_want_write failed with rc %d\n", rc);
  90                return rc;
  91        }
  92
  93        src_file = fdget(srcfd);
  94        if (!src_file.file) {
  95                rc = -EBADF;
  96                goto out_drop_write;
  97        }
  98
  99        if (src_file.file->f_op->unlocked_ioctl != cifs_ioctl) {
 100                rc = -EBADF;
 101                cifs_dbg(VFS, "src file seems to be from a different filesystem type\n");
 102                goto out_fput;
 103        }
 104
 105        src_inode = file_inode(src_file.file);
 106        rc = -EINVAL;
 107        if (S_ISDIR(src_inode->i_mode))
 108                goto out_fput;
 109
 110        rc = cifs_file_copychunk_range(xid, src_file.file, 0, dst_file, 0,
 111                                        src_inode->i_size, 0);
 112        if (rc > 0)
 113                rc = 0;
 114out_fput:
 115        fdput(src_file);
 116out_drop_write:
 117        mnt_drop_write_file(dst_file);
 118        return rc;
 119}
 120
 121static long smb_mnt_get_fsinfo(unsigned int xid, struct cifs_tcon *tcon,
 122                                void __user *arg)
 123{
 124        int rc = 0;
 125        struct smb_mnt_fs_info *fsinf;
 126
 127        fsinf = kzalloc(sizeof(struct smb_mnt_fs_info), GFP_KERNEL);
 128        if (fsinf == NULL)
 129                return -ENOMEM;
 130
 131        fsinf->version = 1;
 132        fsinf->protocol_id = tcon->ses->server->vals->protocol_id;
 133        fsinf->device_characteristics =
 134                        le32_to_cpu(tcon->fsDevInfo.DeviceCharacteristics);
 135        fsinf->device_type = le32_to_cpu(tcon->fsDevInfo.DeviceType);
 136        fsinf->fs_attributes = le32_to_cpu(tcon->fsAttrInfo.Attributes);
 137        fsinf->max_path_component =
 138                le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength);
 139        fsinf->vol_serial_number = tcon->vol_serial_number;
 140        fsinf->vol_create_time = le64_to_cpu(tcon->vol_create_time);
 141        fsinf->share_flags = tcon->share_flags;
 142        fsinf->share_caps = le32_to_cpu(tcon->capabilities);
 143        fsinf->sector_flags = tcon->ss_flags;
 144        fsinf->optimal_sector_size = tcon->perf_sector_size;
 145        fsinf->max_bytes_chunk = tcon->max_bytes_chunk;
 146        fsinf->maximal_access = tcon->maximal_access;
 147        fsinf->cifs_posix_caps = le64_to_cpu(tcon->fsUnixInfo.Capability);
 148
 149        if (copy_to_user(arg, fsinf, sizeof(struct smb_mnt_fs_info)))
 150                rc = -EFAULT;
 151
 152        kfree(fsinf);
 153        return rc;
 154}
 155
 156static int cifs_shutdown(struct super_block *sb, unsigned long arg)
 157{
 158        struct cifs_sb_info *sbi = CIFS_SB(sb);
 159        __u32 flags;
 160
 161        if (!capable(CAP_SYS_ADMIN))
 162                return -EPERM;
 163
 164        if (get_user(flags, (__u32 __user *)arg))
 165                return -EFAULT;
 166
 167        if (flags > CIFS_GOING_FLAGS_NOLOGFLUSH)
 168                return -EINVAL;
 169
 170        if (cifs_forced_shutdown(sbi))
 171                return 0;
 172
 173        cifs_dbg(VFS, "shut down requested (%d)", flags);
 174/*      trace_cifs_shutdown(sb, flags);*/
 175
 176        /*
 177         * see:
 178         *   https://man7.org/linux/man-pages/man2/ioctl_xfs_goingdown.2.html
 179         * for more information and description of original intent of the flags
 180         */
 181        switch (flags) {
 182        /*
 183         * We could add support later for default flag which requires:
 184         *     "Flush all dirty data and metadata to disk"
 185         * would need to call syncfs or equivalent to flush page cache for
 186         * the mount and then issue fsync to server (if nostrictsync not set)
 187         */
 188        case CIFS_GOING_FLAGS_DEFAULT:
 189                cifs_dbg(FYI, "shutdown with default flag not supported\n");
 190                return -EINVAL;
 191        /*
 192         * FLAGS_LOGFLUSH is easy since it asks to write out metadata (not
 193         * data) but metadata writes are not cached on the client, so can treat
 194         * it similarly to NOLOGFLUSH
 195         */
 196        case CIFS_GOING_FLAGS_LOGFLUSH:
 197        case CIFS_GOING_FLAGS_NOLOGFLUSH:
 198                sbi->mnt_cifs_flags |= CIFS_MOUNT_SHUTDOWN;
 199                return 0;
 200        default:
 201                return -EINVAL;
 202        }
 203        return 0;
 204}
 205
 206static int cifs_dump_full_key(struct cifs_tcon *tcon, struct smb3_full_key_debug_info __user *in)
 207{
 208        struct smb3_full_key_debug_info out;
 209        struct cifs_ses *ses;
 210        int rc = 0;
 211        bool found = false;
 212        u8 __user *end;
 213
 214        if (!smb3_encryption_required(tcon)) {
 215                rc = -EOPNOTSUPP;
 216                goto out;
 217        }
 218
 219        /* copy user input into our output buffer */
 220        if (copy_from_user(&out, in, sizeof(out))) {
 221                rc = -EINVAL;
 222                goto out;
 223        }
 224
 225        if (!out.session_id) {
 226                /* if ses id is 0, use current user session */
 227                ses = tcon->ses;
 228        } else {
 229                /* otherwise if a session id is given, look for it in all our sessions */
 230                struct cifs_ses *ses_it = NULL;
 231                struct TCP_Server_Info *server_it = NULL;
 232
 233                spin_lock(&cifs_tcp_ses_lock);
 234                list_for_each_entry(server_it, &cifs_tcp_ses_list, tcp_ses_list) {
 235                        list_for_each_entry(ses_it, &server_it->smb_ses_list, smb_ses_list) {
 236                                if (ses_it->Suid == out.session_id) {
 237                                        ses = ses_it;
 238                                        /*
 239                                         * since we are using the session outside the crit
 240                                         * section, we need to make sure it won't be released
 241                                         * so increment its refcount
 242                                         */
 243                                        ses->ses_count++;
 244                                        found = true;
 245                                        goto search_end;
 246                                }
 247                        }
 248                }
 249search_end:
 250                spin_unlock(&cifs_tcp_ses_lock);
 251                if (!found) {
 252                        rc = -ENOENT;
 253                        goto out;
 254                }
 255        }
 256
 257        switch (ses->server->cipher_type) {
 258        case SMB2_ENCRYPTION_AES128_CCM:
 259        case SMB2_ENCRYPTION_AES128_GCM:
 260                out.session_key_length = CIFS_SESS_KEY_SIZE;
 261                out.server_in_key_length = out.server_out_key_length = SMB3_GCM128_CRYPTKEY_SIZE;
 262                break;
 263        case SMB2_ENCRYPTION_AES256_CCM:
 264        case SMB2_ENCRYPTION_AES256_GCM:
 265                out.session_key_length = CIFS_SESS_KEY_SIZE;
 266                out.server_in_key_length = out.server_out_key_length = SMB3_GCM256_CRYPTKEY_SIZE;
 267                break;
 268        default:
 269                rc = -EOPNOTSUPP;
 270                goto out;
 271        }
 272
 273        /* check if user buffer is big enough to store all the keys */
 274        if (out.in_size < sizeof(out) + out.session_key_length + out.server_in_key_length
 275            + out.server_out_key_length) {
 276                rc = -ENOBUFS;
 277                goto out;
 278        }
 279
 280        out.session_id = ses->Suid;
 281        out.cipher_type = le16_to_cpu(ses->server->cipher_type);
 282
 283        /* overwrite user input with our output */
 284        if (copy_to_user(in, &out, sizeof(out))) {
 285                rc = -EINVAL;
 286                goto out;
 287        }
 288
 289        /* append all the keys at the end of the user buffer */
 290        end = in->data;
 291        if (copy_to_user(end, ses->auth_key.response, out.session_key_length)) {
 292                rc = -EINVAL;
 293                goto out;
 294        }
 295        end += out.session_key_length;
 296
 297        if (copy_to_user(end, ses->smb3encryptionkey, out.server_in_key_length)) {
 298                rc = -EINVAL;
 299                goto out;
 300        }
 301        end += out.server_in_key_length;
 302
 303        if (copy_to_user(end, ses->smb3decryptionkey, out.server_out_key_length)) {
 304                rc = -EINVAL;
 305                goto out;
 306        }
 307
 308out:
 309        if (found)
 310                cifs_put_smb_ses(ses);
 311        return rc;
 312}
 313
 314long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
 315{
 316        struct inode *inode = file_inode(filep);
 317        struct smb3_key_debug_info pkey_inf;
 318        int rc = -ENOTTY; /* strange error - but the precedent */
 319        unsigned int xid;
 320        struct cifsFileInfo *pSMBFile = filep->private_data;
 321        struct cifs_tcon *tcon;
 322        struct tcon_link *tlink;
 323        struct cifs_sb_info *cifs_sb;
 324        __u64   ExtAttrBits = 0;
 325        __u64   caps;
 326
 327        xid = get_xid();
 328
 329        cifs_dbg(FYI, "cifs ioctl 0x%x\n", command);
 330        switch (command) {
 331                case FS_IOC_GETFLAGS:
 332                        if (pSMBFile == NULL)
 333                                break;
 334                        tcon = tlink_tcon(pSMBFile->tlink);
 335                        caps = le64_to_cpu(tcon->fsUnixInfo.Capability);
 336#ifdef CONFIG_CIFS_POSIX
 337                        if (CIFS_UNIX_EXTATTR_CAP & caps) {
 338                                __u64   ExtAttrMask = 0;
 339                                rc = CIFSGetExtAttr(xid, tcon,
 340                                                    pSMBFile->fid.netfid,
 341                                                    &ExtAttrBits, &ExtAttrMask);
 342                                if (rc == 0)
 343                                        rc = put_user(ExtAttrBits &
 344                                                FS_FL_USER_VISIBLE,
 345                                                (int __user *)arg);
 346                                if (rc != EOPNOTSUPP)
 347                                        break;
 348                        }
 349#endif /* CONFIG_CIFS_POSIX */
 350                        rc = 0;
 351                        if (CIFS_I(inode)->cifsAttrs & ATTR_COMPRESSED) {
 352                                /* add in the compressed bit */
 353                                ExtAttrBits = FS_COMPR_FL;
 354                                rc = put_user(ExtAttrBits & FS_FL_USER_VISIBLE,
 355                                              (int __user *)arg);
 356                        }
 357                        break;
 358                case FS_IOC_SETFLAGS:
 359                        if (pSMBFile == NULL)
 360                                break;
 361                        tcon = tlink_tcon(pSMBFile->tlink);
 362                        caps = le64_to_cpu(tcon->fsUnixInfo.Capability);
 363
 364                        if (get_user(ExtAttrBits, (int __user *)arg)) {
 365                                rc = -EFAULT;
 366                                break;
 367                        }
 368
 369                        /*
 370                         * if (CIFS_UNIX_EXTATTR_CAP & caps)
 371                         *      rc = CIFSSetExtAttr(xid, tcon,
 372                         *                     pSMBFile->fid.netfid,
 373                         *                     extAttrBits,
 374                         *                     &ExtAttrMask);
 375                         * if (rc != EOPNOTSUPP)
 376                         *      break;
 377                         */
 378
 379                        /* Currently only flag we can set is compressed flag */
 380                        if ((ExtAttrBits & FS_COMPR_FL) == 0)
 381                                break;
 382
 383                        /* Try to set compress flag */
 384                        if (tcon->ses->server->ops->set_compression) {
 385                                rc = tcon->ses->server->ops->set_compression(
 386                                                        xid, tcon, pSMBFile);
 387                                cifs_dbg(FYI, "set compress flag rc %d\n", rc);
 388                        }
 389                        break;
 390                case CIFS_IOC_COPYCHUNK_FILE:
 391                        rc = cifs_ioctl_copychunk(xid, filep, arg);
 392                        break;
 393                case CIFS_QUERY_INFO:
 394                        rc = cifs_ioctl_query_info(xid, filep, arg);
 395                        break;
 396                case CIFS_IOC_SET_INTEGRITY:
 397                        if (pSMBFile == NULL)
 398                                break;
 399                        tcon = tlink_tcon(pSMBFile->tlink);
 400                        if (tcon->ses->server->ops->set_integrity)
 401                                rc = tcon->ses->server->ops->set_integrity(xid,
 402                                                tcon, pSMBFile);
 403                        else
 404                                rc = -EOPNOTSUPP;
 405                        break;
 406                case CIFS_IOC_GET_MNT_INFO:
 407                        if (pSMBFile == NULL)
 408                                break;
 409                        tcon = tlink_tcon(pSMBFile->tlink);
 410                        rc = smb_mnt_get_fsinfo(xid, tcon, (void __user *)arg);
 411                        break;
 412                case CIFS_ENUMERATE_SNAPSHOTS:
 413                        if (pSMBFile == NULL)
 414                                break;
 415                        if (arg == 0) {
 416                                rc = -EINVAL;
 417                                goto cifs_ioc_exit;
 418                        }
 419                        tcon = tlink_tcon(pSMBFile->tlink);
 420                        if (tcon->ses->server->ops->enum_snapshots)
 421                                rc = tcon->ses->server->ops->enum_snapshots(xid, tcon,
 422                                                pSMBFile, (void __user *)arg);
 423                        else
 424                                rc = -EOPNOTSUPP;
 425                        break;
 426                case CIFS_DUMP_KEY:
 427                        /*
 428                         * Dump encryption keys. This is an old ioctl that only
 429                         * handles AES-128-{CCM,GCM}.
 430                         */
 431                        if (pSMBFile == NULL)
 432                                break;
 433                        if (!capable(CAP_SYS_ADMIN)) {
 434                                rc = -EACCES;
 435                                break;
 436                        }
 437
 438                        tcon = tlink_tcon(pSMBFile->tlink);
 439                        if (!smb3_encryption_required(tcon)) {
 440                                rc = -EOPNOTSUPP;
 441                                break;
 442                        }
 443                        pkey_inf.cipher_type =
 444                                le16_to_cpu(tcon->ses->server->cipher_type);
 445                        pkey_inf.Suid = tcon->ses->Suid;
 446                        memcpy(pkey_inf.auth_key, tcon->ses->auth_key.response,
 447                                        16 /* SMB2_NTLMV2_SESSKEY_SIZE */);
 448                        memcpy(pkey_inf.smb3decryptionkey,
 449                              tcon->ses->smb3decryptionkey, SMB3_SIGN_KEY_SIZE);
 450                        memcpy(pkey_inf.smb3encryptionkey,
 451                              tcon->ses->smb3encryptionkey, SMB3_SIGN_KEY_SIZE);
 452                        if (copy_to_user((void __user *)arg, &pkey_inf,
 453                                        sizeof(struct smb3_key_debug_info)))
 454                                rc = -EFAULT;
 455                        else
 456                                rc = 0;
 457                        break;
 458                case CIFS_DUMP_FULL_KEY:
 459                        /*
 460                         * Dump encryption keys (handles any key sizes)
 461                         */
 462                        if (pSMBFile == NULL)
 463                                break;
 464                        if (!capable(CAP_SYS_ADMIN)) {
 465                                rc = -EACCES;
 466                                break;
 467                        }
 468                        tcon = tlink_tcon(pSMBFile->tlink);
 469                        rc = cifs_dump_full_key(tcon, (void __user *)arg);
 470                        break;
 471                case CIFS_IOC_NOTIFY:
 472                        if (!S_ISDIR(inode->i_mode)) {
 473                                /* Notify can only be done on directories */
 474                                rc = -EOPNOTSUPP;
 475                                break;
 476                        }
 477                        cifs_sb = CIFS_SB(inode->i_sb);
 478                        tlink = cifs_sb_tlink(cifs_sb);
 479                        if (IS_ERR(tlink)) {
 480                                rc = PTR_ERR(tlink);
 481                                break;
 482                        }
 483                        tcon = tlink_tcon(tlink);
 484                        if (tcon && tcon->ses->server->ops->notify) {
 485                                rc = tcon->ses->server->ops->notify(xid,
 486                                                filep, (void __user *)arg);
 487                                cifs_dbg(FYI, "ioctl notify rc %d\n", rc);
 488                        } else
 489                                rc = -EOPNOTSUPP;
 490                        cifs_put_tlink(tlink);
 491                        break;
 492                case CIFS_IOC_SHUTDOWN:
 493                        rc = cifs_shutdown(inode->i_sb, arg);
 494                        break;
 495                default:
 496                        cifs_dbg(FYI, "unsupported ioctl\n");
 497                        break;
 498        }
 499cifs_ioc_exit:
 500        free_xid(xid);
 501        return rc;
 502}
 503