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