Linux 6.9-rc1
[linux-2.6-microblaze.git] / fs / open.c
index 8a813fa..ee8460c 100644 (file)
--- a/fs/open.c
+++ b/fs/open.c
 #include <linux/audit.h>
 #include <linux/falloc.h>
 #include <linux/fs_struct.h>
-#include <linux/ima.h>
 #include <linux/dnotify.h>
 #include <linux/compat.h>
 #include <linux/mnt_idmapping.h>
+#include <linux/filelock.h>
 
 #include "internal.h"
 
-int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry,
+int do_truncate(struct mnt_idmap *idmap, struct dentry *dentry,
                loff_t length, unsigned int time_attrs, struct file *filp)
 {
        int ret;
@@ -54,7 +54,7 @@ int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry,
        }
 
        /* Remove suid, sgid, and file capabilities on truncate too */
-       ret = dentry_needs_remove_privs(dentry);
+       ret = dentry_needs_remove_privs(idmap, dentry);
        if (ret < 0)
                return ret;
        if (ret)
@@ -62,14 +62,14 @@ int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry,
 
        inode_lock(dentry->d_inode);
        /* Note any delegations or leases have already been broken: */
-       ret = notify_change(mnt_userns, dentry, &newattrs, NULL);
+       ret = notify_change(idmap, dentry, &newattrs, NULL);
        inode_unlock(dentry->d_inode);
        return ret;
 }
 
 long vfs_truncate(const struct path *path, loff_t length)
 {
-       struct user_namespace *mnt_userns;
+       struct mnt_idmap *idmap;
        struct inode *inode;
        long error;
 
@@ -85,8 +85,8 @@ long vfs_truncate(const struct path *path, loff_t length)
        if (error)
                goto out;
 
-       mnt_userns = mnt_user_ns(path->mnt);
-       error = inode_permission(mnt_userns, inode, MAY_WRITE);
+       idmap = mnt_idmap(path->mnt);
+       error = inode_permission(idmap, inode, MAY_WRITE);
        if (error)
                goto mnt_drop_write_and_out;
 
@@ -108,7 +108,7 @@ long vfs_truncate(const struct path *path, loff_t length)
 
        error = security_path_truncate(path);
        if (!error)
-               error = do_truncate(mnt_userns, path->dentry, length, 0, NULL);
+               error = do_truncate(idmap, path->dentry, length, 0, NULL);
 
 put_write_and_out:
        put_write_access(inode);
@@ -153,49 +153,52 @@ COMPAT_SYSCALL_DEFINE2(truncate, const char __user *, path, compat_off_t, length
 }
 #endif
 
-long do_sys_ftruncate(unsigned int fd, loff_t length, int small)
+long do_ftruncate(struct file *file, loff_t length, int small)
 {
        struct inode *inode;
        struct dentry *dentry;
-       struct fd f;
        int error;
 
-       error = -EINVAL;
-       if (length < 0)
-               goto out;
-       error = -EBADF;
-       f = fdget(fd);
-       if (!f.file)
-               goto out;
-
        /* explicitly opened as large or we are on 64-bit box */
-       if (f.file->f_flags & O_LARGEFILE)
+       if (file->f_flags & O_LARGEFILE)
                small = 0;
 
-       dentry = f.file->f_path.dentry;
+       dentry = file->f_path.dentry;
        inode = dentry->d_inode;
-       error = -EINVAL;
-       if (!S_ISREG(inode->i_mode) || !(f.file->f_mode & FMODE_WRITE))
-               goto out_putf;
+       if (!S_ISREG(inode->i_mode) || !(file->f_mode & FMODE_WRITE))
+               return -EINVAL;
 
-       error = -EINVAL;
        /* Cannot ftruncate over 2^31 bytes without large file support */
        if (small && length > MAX_NON_LFS)
-               goto out_putf;
+               return -EINVAL;
 
-       error = -EPERM;
        /* Check IS_APPEND on real upper inode */
-       if (IS_APPEND(file_inode(f.file)))
-               goto out_putf;
+       if (IS_APPEND(file_inode(file)))
+               return -EPERM;
        sb_start_write(inode->i_sb);
-       error = security_path_truncate(&f.file->f_path);
+       error = security_file_truncate(file);
        if (!error)
-               error = do_truncate(file_mnt_user_ns(f.file), dentry, length,
-                                   ATTR_MTIME | ATTR_CTIME, f.file);
+               error = do_truncate(file_mnt_idmap(file), dentry, length,
+                                   ATTR_MTIME | ATTR_CTIME, file);
        sb_end_write(inode->i_sb);
-out_putf:
+
+       return error;
+}
+
+long do_sys_ftruncate(unsigned int fd, loff_t length, int small)
+{
+       struct fd f;
+       int error;
+
+       if (length < 0)
+               return -EINVAL;
+       f = fdget(fd);
+       if (!f.file)
+               return -EBADF;
+
+       error = do_ftruncate(f.file, length, small);
+
        fdput(f);
-out:
        return error;
 }
 
@@ -303,6 +306,10 @@ int vfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
        if (ret)
                return ret;
 
+       ret = fsnotify_file_area_perm(file, MAY_WRITE, &offset, len);
+       if (ret)
+               return ret;
+
        if (S_ISFIFO(inode->i_mode))
                return -ESPIPE;
 
@@ -367,7 +374,37 @@ COMPAT_SYSCALL_DEFINE6(fallocate, int, fd, int, mode, compat_arg_u64_dual(offset
  * access() needs to use the real uid/gid, not the effective uid/gid.
  * We do this by temporarily clearing all FS-related capabilities and
  * switching the fsuid/fsgid around to the real ones.
+ *
+ * Creating new credentials is expensive, so we try to skip doing it,
+ * which we can if the result would match what we already got.
  */
+static bool access_need_override_creds(int flags)
+{
+       const struct cred *cred;
+
+       if (flags & AT_EACCESS)
+               return false;
+
+       cred = current_cred();
+       if (!uid_eq(cred->fsuid, cred->uid) ||
+           !gid_eq(cred->fsgid, cred->gid))
+               return true;
+
+       if (!issecure(SECURE_NO_SETUID_FIXUP)) {
+               kuid_t root_uid = make_kuid(cred->user_ns, 0);
+               if (!uid_eq(cred->uid, root_uid)) {
+                       if (!cap_isclear(cred->cap_effective))
+                               return true;
+               } else {
+                       if (!cap_isidentical(cred->cap_effective,
+                           cred->cap_permitted))
+                               return true;
+               }
+       }
+
+       return false;
+}
+
 static const struct cred *access_override_creds(void)
 {
        const struct cred *old_cred;
@@ -377,6 +414,12 @@ static const struct cred *access_override_creds(void)
        if (!override_cred)
                return NULL;
 
+       /*
+        * XXX access_need_override_creds performs checks in hopes of skipping
+        * this work. Make sure it stays in sync if making any changes in this
+        * routine.
+        */
+
        override_cred->fsuid = override_cred->uid;
        override_cred->fsgid = override_cred->gid;
 
@@ -405,7 +448,8 @@ static const struct cred *access_override_creds(void)
         * 'get_current_cred()' function), that will clear the
         * non_rcu field, because now that other user may be
         * expecting RCU freeing. But normal thread-synchronous
-        * cred accesses will keep things non-RCY.
+        * cred accesses will keep things non-racy to avoid RCU
+        * freeing.
         */
        override_cred->non_rcu = 1;
 
@@ -436,7 +480,7 @@ static long do_faccessat(int dfd, const char __user *filename, int mode, int fla
        if (flags & AT_EMPTY_PATH)
                lookup_flags |= LOOKUP_EMPTY;
 
-       if (!(flags & AT_EACCESS)) {
+       if (access_need_override_creds(flags)) {
                old_cred = access_override_creds();
                if (!old_cred)
                        return -ENOMEM;
@@ -459,7 +503,7 @@ retry:
                        goto out_path_release;
        }
 
-       res = inode_permission(mnt_user_ns(path.mnt), inode, mode | MAY_ACCESS);
+       res = inode_permission(mnt_idmap(path.mnt), inode, mode | MAY_ACCESS);
        /* SuS v2 requires we report a read only fs too */
        if (res || !(mode & S_IWOTH) || special_file(inode->i_mode))
                goto out_path_release;
@@ -603,7 +647,7 @@ retry_deleg:
                goto out_unlock;
        newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
        newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
-       error = notify_change(mnt_user_ns(path->mnt), path->dentry,
+       error = notify_change(mnt_idmap(path->mnt), path->dentry,
                              &newattrs, &delegated_inode);
 out_unlock:
        inode_unlock(inode);
@@ -634,11 +678,20 @@ SYSCALL_DEFINE2(fchmod, unsigned int, fd, umode_t, mode)
        return err;
 }
 
-static int do_fchmodat(int dfd, const char __user *filename, umode_t mode)
+static int do_fchmodat(int dfd, const char __user *filename, umode_t mode,
+                      unsigned int flags)
 {
        struct path path;
        int error;
-       unsigned int lookup_flags = LOOKUP_FOLLOW;
+       unsigned int lookup_flags;
+
+       if (unlikely(flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)))
+               return -EINVAL;
+
+       lookup_flags = (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
+       if (flags & AT_EMPTY_PATH)
+               lookup_flags |= LOOKUP_EMPTY;
+
 retry:
        error = user_path_at(dfd, filename, lookup_flags, &path);
        if (!error) {
@@ -652,21 +705,24 @@ retry:
        return error;
 }
 
+SYSCALL_DEFINE4(fchmodat2, int, dfd, const char __user *, filename,
+               umode_t, mode, unsigned int, flags)
+{
+       return do_fchmodat(dfd, filename, mode, flags);
+}
+
 SYSCALL_DEFINE3(fchmodat, int, dfd, const char __user *, filename,
                umode_t, mode)
 {
-       return do_fchmodat(dfd, filename, mode);
+       return do_fchmodat(dfd, filename, mode, 0);
 }
 
 SYSCALL_DEFINE2(chmod, const char __user *, filename, umode_t, mode)
 {
-       return do_fchmodat(AT_FDCWD, filename, mode);
+       return do_fchmodat(AT_FDCWD, filename, mode, 0);
 }
 
-/**
- * setattr_vfsuid - check and set ia_fsuid attribute
- * @kuid: new inode owner
- *
+/*
  * Check whether @kuid is valid and if so generate and set vfsuid_t in
  * ia_vfsuid.
  *
@@ -681,10 +737,7 @@ static inline bool setattr_vfsuid(struct iattr *attr, kuid_t kuid)
        return true;
 }
 
-/**
- * setattr_vfsgid - check and set ia_fsgid attribute
- * @kgid: new inode owner
- *
+/*
  * Check whether @kgid is valid and if so generate and set vfsgid_t in
  * ia_vfsgid.
  *
@@ -701,7 +754,8 @@ static inline bool setattr_vfsgid(struct iattr *attr, kgid_t kgid)
 
 int chown_common(const struct path *path, uid_t user, gid_t group)
 {
-       struct user_namespace *mnt_userns, *fs_userns;
+       struct mnt_idmap *idmap;
+       struct user_namespace *fs_userns;
        struct inode *inode = path->dentry->d_inode;
        struct inode *delegated_inode = NULL;
        int error;
@@ -712,26 +766,28 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
        uid = make_kuid(current_user_ns(), user);
        gid = make_kgid(current_user_ns(), group);
 
-       mnt_userns = mnt_user_ns(path->mnt);
+       idmap = mnt_idmap(path->mnt);
        fs_userns = i_user_ns(inode);
 
 retry_deleg:
+       newattrs.ia_vfsuid = INVALID_VFSUID;
+       newattrs.ia_vfsgid = INVALID_VFSGID;
        newattrs.ia_valid =  ATTR_CTIME;
        if ((user != (uid_t)-1) && !setattr_vfsuid(&newattrs, uid))
                return -EINVAL;
        if ((group != (gid_t)-1) && !setattr_vfsgid(&newattrs, gid))
                return -EINVAL;
-       if (!S_ISDIR(inode->i_mode))
-               newattrs.ia_valid |=
-                       ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV;
        inode_lock(inode);
+       if (!S_ISDIR(inode->i_mode))
+               newattrs.ia_valid |= ATTR_KILL_SUID | ATTR_KILL_PRIV |
+                                    setattr_should_drop_sgid(idmap, inode);
        /* Continue to send actual fs values, not the mount values. */
        error = security_path_chown(
                path,
-               from_vfsuid(mnt_userns, fs_userns, newattrs.ia_vfsuid),
-               from_vfsgid(mnt_userns, fs_userns, newattrs.ia_vfsgid));
+               from_vfsuid(idmap, fs_userns, newattrs.ia_vfsuid),
+               from_vfsgid(idmap, fs_userns, newattrs.ia_vfsgid));
        if (!error)
-               error = notify_change(mnt_userns, path->dentry, &newattrs,
+               error = notify_change(idmap, path->dentry, &newattrs,
                                      &delegated_inode);
        inode_unlock(inode);
        if (delegated_inode) {
@@ -821,6 +877,30 @@ SYSCALL_DEFINE3(fchown, unsigned int, fd, uid_t, user, gid_t, group)
        return ksys_fchown(fd, user, group);
 }
 
+static inline int file_get_write_access(struct file *f)
+{
+       int error;
+
+       error = get_write_access(f->f_inode);
+       if (unlikely(error))
+               return error;
+       error = mnt_get_write_access(f->f_path.mnt);
+       if (unlikely(error))
+               goto cleanup_inode;
+       if (unlikely(f->f_mode & FMODE_BACKING)) {
+               error = mnt_get_write_access(backing_file_user_path(f)->mnt);
+               if (unlikely(error))
+                       goto cleanup_mnt;
+       }
+       return 0;
+
+cleanup_mnt:
+       mnt_put_write_access(f->f_path.mnt);
+cleanup_inode:
+       put_write_access(f->f_inode);
+       return error;
+}
+
 static int do_dentry_open(struct file *f,
                          struct inode *inode,
                          int (*open)(struct inode *, struct file *))
@@ -840,15 +920,12 @@ static int do_dentry_open(struct file *f,
                return 0;
        }
 
-       if (f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {
-               error = get_write_access(inode);
+       if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) {
+               i_readcount_inc(inode);
+       } else if (f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {
+               error = file_get_write_access(f);
                if (unlikely(error))
                        goto cleanup_file;
-               error = __mnt_want_write(f->f_path.mnt);
-               if (unlikely(error)) {
-                       put_write_access(inode);
-                       goto cleanup_file;
-               }
                f->f_mode |= FMODE_WRITER;
        }
 
@@ -866,7 +943,7 @@ static int do_dentry_open(struct file *f,
        if (error)
                goto cleanup_all;
 
-       error = break_lease(locks_inode(f), f->f_flags);
+       error = break_lease(file_inode(f), f->f_flags);
        if (error)
                goto cleanup_all;
 
@@ -880,8 +957,6 @@ static int do_dentry_open(struct file *f,
                        goto cleanup_all;
        }
        f->f_mode |= FMODE_OPENED;
-       if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
-               i_readcount_inc(inode);
        if ((f->f_mode & FMODE_READ) &&
             likely(f->f_op->read || f->f_op->read_iter))
                f->f_mode |= FMODE_CAN_READ;
@@ -929,16 +1004,18 @@ static int do_dentry_open(struct file *f,
                }
        }
 
+       /*
+        * Once we return a file with FMODE_OPENED, __fput() will call
+        * fsnotify_close(), so we need fsnotify_open() here for symmetry.
+        */
+       fsnotify_open(f);
        return 0;
 
 cleanup_all:
        if (WARN_ON_ONCE(error > 0))
                error = -EINVAL;
        fops_put(f->f_op);
-       if (f->f_mode & FMODE_WRITER) {
-               put_write_access(inode);
-               __mnt_drop_write(f->f_path.mnt);
-       }
+       put_file_access(f);
 cleanup_file:
        path_put(&f->f_path);
        f->f_path.mnt = NULL;
@@ -952,7 +1029,6 @@ cleanup_file:
  * @file: file pointer
  * @dentry: pointer to dentry
  * @open: open callback
- * @opened: state of open
  *
  * This can be used to finish opening a file passed to i_op->atomic_open().
  *
@@ -1006,7 +1082,6 @@ EXPORT_SYMBOL(file_path);
  * vfs_open - open the file at the given path
  * @path: path to open
  * @file: newly allocated file with f_flag initialized
- * @cred: credentials to use
  */
 int vfs_open(const struct path *path, struct file *file)
 {
@@ -1020,8 +1095,6 @@ struct file *dentry_open(const struct path *path, int flags,
        int error;
        struct file *f;
 
-       validate_creds(cred);
-
        /* We must always pass in a valid mount pointer. */
        BUG_ON(!path->mnt);
 
@@ -1060,12 +1133,11 @@ struct file *dentry_create(const struct path *path, int flags, umode_t mode,
        struct file *f;
        int error;
 
-       validate_creds(cred);
        f = alloc_empty_file(flags, cred);
        if (IS_ERR(f))
                return f;
 
-       error = vfs_create(mnt_user_ns(path->mnt),
+       error = vfs_create(mnt_idmap(path->mnt),
                           d_inode(path->dentry->d_parent),
                           path->dentry, mode, true);
        if (!error)
@@ -1079,23 +1151,38 @@ struct file *dentry_create(const struct path *path, int flags, umode_t mode,
 }
 EXPORT_SYMBOL(dentry_create);
 
-struct file *open_with_fake_path(const struct path *path, int flags,
+/**
+ * kernel_file_open - open a file for kernel internal use
+ * @path:      path of the file to open
+ * @flags:     open flags
+ * @inode:     the inode
+ * @cred:      credentials for open
+ *
+ * Open a file for use by in-kernel consumers. The file is not accounted
+ * against nr_files and must not be installed into the file descriptor
+ * table.
+ *
+ * Return: Opened file on success, an error pointer on failure.
+ */
+struct file *kernel_file_open(const struct path *path, int flags,
                                struct inode *inode, const struct cred *cred)
 {
-       struct file *f = alloc_empty_file_noaccount(flags, cred);
-       if (!IS_ERR(f)) {
-               int error;
+       struct file *f;
+       int error;
 
-               f->f_path = *path;
-               error = do_dentry_open(f, inode, NULL);
-               if (error) {
-                       fput(f);
-                       f = ERR_PTR(error);
-               }
+       f = alloc_empty_file_noaccount(flags, cred);
+       if (IS_ERR(f))
+               return f;
+
+       f->f_path = *path;
+       error = do_dentry_open(f, inode, NULL);
+       if (error) {
+               fput(f);
+               f = ERR_PTR(error);
        }
        return f;
 }
-EXPORT_SYMBOL(open_with_fake_path);
+EXPORT_SYMBOL_GPL(kernel_file_open);
 
 #define WILL_CREATE(flags)     (flags & (O_CREAT | __O_TMPFILE))
 #define O_PATH_FLAGS           (O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC)
@@ -1119,7 +1206,7 @@ inline struct open_how build_open_how(int flags, umode_t mode)
 inline int build_open_flags(const struct open_how *how, struct open_flags *op)
 {
        u64 flags = how->flags;
-       u64 strip = FMODE_NONOTIFY | O_CLOEXEC;
+       u64 strip = __FMODE_NONOTIFY | O_CLOEXEC;
        int lookup_flags = 0;
        int acc_mode = ACC_MODE(flags);
 
@@ -1159,13 +1246,21 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
        }
 
        /*
-        * In order to ensure programs get explicit errors when trying to use
-        * O_TMPFILE on old kernels, O_TMPFILE is implemented such that it
-        * looks like (O_DIRECTORY|O_RDWR & ~O_CREAT) to old kernels. But we
-        * have to require userspace to explicitly set it.
+        * Block bugs where O_DIRECTORY | O_CREAT created regular files.
+        * Note, that blocking O_DIRECTORY | O_CREAT here also protects
+        * O_TMPFILE below which requires O_DIRECTORY being raised.
         */
+       if ((flags & (O_DIRECTORY | O_CREAT)) == (O_DIRECTORY | O_CREAT))
+               return -EINVAL;
+
+       /* Now handle the creative implementation of O_TMPFILE. */
        if (flags & __O_TMPFILE) {
-               if ((flags & O_TMPFILE_MASK) != O_TMPFILE)
+               /*
+                * In order to ensure programs get explicit errors when trying
+                * to use O_TMPFILE on old kernels we enforce that O_DIRECTORY
+                * is raised alongside __O_TMPFILE.
+                */
+               if (!(flags & O_DIRECTORY))
                        return -EINVAL;
                if (!(acc_mode & MAY_WRITE))
                        return -EINVAL;
@@ -1226,7 +1321,7 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
                lookup_flags |= LOOKUP_IN_ROOT;
        if (how->resolve & RESOLVE_CACHED) {
                /* Don't bother even trying for create/truncate/tmpfile open */
-               if (flags & (O_TRUNC | O_CREAT | O_TMPFILE))
+               if (flags & (O_TRUNC | O_CREAT | __O_TMPFILE))
                        return -EAGAIN;
                lookup_flags |= LOOKUP_CACHED;
        }
@@ -1271,7 +1366,7 @@ struct file *filp_open(const char *filename, int flags, umode_t mode)
 {
        struct filename *name = getname_kernel(filename);
        struct file *file = ERR_CAST(name);
-       
+
        if (!IS_ERR(name)) {
                file = file_open_name(name, flags, mode);
                putname(name);
@@ -1313,7 +1408,6 @@ static long do_sys_openat2(int dfd, const char __user *filename,
                        put_unused_fd(fd);
                        fd = PTR_ERR(f);
                } else {
-                       fsnotify_open(f);
                        fd_install(fd, f);
                }
        }
@@ -1408,12 +1502,13 @@ SYSCALL_DEFINE2(creat, const char __user *, pathname, umode_t, mode)
  * "id" is the POSIX thread ID. We use the
  * files pointer for this..
  */
-int filp_close(struct file *filp, fl_owner_t id)
+static int filp_flush(struct file *filp, fl_owner_t id)
 {
        int retval = 0;
 
-       if (!file_count(filp)) {
-               printk(KERN_ERR "VFS: Close: file count is 0\n");
+       if (CHECK_DATA_CORRUPTION(file_count(filp) == 0,
+                       "VFS: Close: file count is 0 (f_op=%ps)",
+                       filp->f_op)) {
                return 0;
        }
 
@@ -1424,10 +1519,18 @@ int filp_close(struct file *filp, fl_owner_t id)
                dnotify_flush(filp, id);
                locks_remove_posix(filp, id);
        }
-       fput(filp);
        return retval;
 }
 
+int filp_close(struct file *filp, fl_owner_t id)
+{
+       int retval;
+
+       retval = filp_flush(filp, id);
+       fput(filp);
+
+       return retval;
+}
 EXPORT_SYMBOL(filp_close);
 
 /*
@@ -1437,7 +1540,20 @@ EXPORT_SYMBOL(filp_close);
  */
 SYSCALL_DEFINE1(close, unsigned int, fd)
 {
-       int retval = close_fd(fd);
+       int retval;
+       struct file *file;
+
+       file = file_close_fd(fd);
+       if (!file)
+               return -EBADF;
+
+       retval = filp_flush(file, current->files);
+
+       /*
+        * We're returning to user space. Don't bother
+        * with any delayed fput() cases.
+        */
+       __fput_sync(file);
 
        /* can't restart close syscall because file table entry was cleared */
        if (unlikely(retval == -ERESTARTSYS ||
@@ -1450,7 +1566,7 @@ SYSCALL_DEFINE1(close, unsigned int, fd)
 }
 
 /**
- * close_range() - Close all file descriptors in a given range.
+ * sys_close_range() - Close all file descriptors in a given range.
  *
  * @fd:     starting file descriptor to close
  * @max_fd: last file descriptor to close