Ensure vfs_chown_fsp() is safe against races.
authorJeremy Allison <jra@samba.org>
Fri, 15 Apr 2011 19:21:39 +0000 (12:21 -0700)
committerJeremy Allison <jra@samba.org>
Fri, 15 Apr 2011 19:21:39 +0000 (12:21 -0700)
source3/smbd/open.c
source3/smbd/proto.h
source3/smbd/vfs.c

index b7c8540da16548c404ace7362fe919746fcb0a1b..14e6bf9b4e6570ae9ad9a027e2c03591e41846a4 100644 (file)
@@ -2563,7 +2563,7 @@ static NTSTATUS mkdir_internal(connection_struct *conn,
  Ensure we didn't get symlink raced on opening a directory.
 ****************************************************************************/
 
-static bool check_same_stat(const SMB_STRUCT_STAT *sbuf1,
+bool check_same_stat(const SMB_STRUCT_STAT *sbuf1,
                        const SMB_STRUCT_STAT *sbuf2)
 {
        if (sbuf1->st_ex_uid != sbuf2->st_ex_uid ||
index c09720267617305953751320a3fd28a474be9287..36eea5e16ee549bdd5ad3090a3c734bb5b6e0a0b 100644 (file)
@@ -567,6 +567,8 @@ void remove_deferred_open_entry(struct file_id id, uint64_t mid,
 NTSTATUS open_file_fchmod(connection_struct *conn,
                          struct smb_filename *smb_fname,
                          files_struct **result);
+bool check_same_stat(const SMB_STRUCT_STAT *sbuf1,
+                       const SMB_STRUCT_STAT *sbuf2);
 NTSTATUS create_directory(connection_struct *conn, struct smb_request *req,
                          struct smb_filename *smb_dname);
 void msg_file_was_renamed(struct messaging_context *msg,
index d8c57a6a86c8ddd7890ba920289b7a083909d81e..0438f7934edd35cf326d3815fac3e5aef573731d 100644 (file)
@@ -1455,6 +1455,11 @@ int smb_vfs_call_lchown(struct vfs_handle_struct *handle, const char *path,
 NTSTATUS vfs_chown_fsp(files_struct *fsp, uid_t uid, gid_t gid)
 {
        int ret;
+       bool as_root = false;
+       const char *path;
+       char *saved_dir = NULL;
+       char *parent_dir = NULL;
+       NTSTATUS status;
 
        if (fsp->fh->fd != -1) {
                /* Try fchown. */
@@ -1467,19 +1472,80 @@ NTSTATUS vfs_chown_fsp(files_struct *fsp, uid_t uid, gid_t gid)
                }
        }
 
-       if (fsp->posix_open) {
+       as_root = (geteuid() == 0);
+
+       if (as_root) {
+               /*
+                * We are being asked to chown as root. Make
+                * sure we chdir() into the path to pin it,
+                * and always act using lchown to ensure we
+                * don't deref any symbolic links.
+                */
+               const char *final_component = NULL;
+               struct smb_filename local_fname;
+
+               saved_dir = vfs_GetWd(talloc_tos(),fsp->conn);
+               if (!saved_dir) {
+                       status = map_nt_error_from_unix(errno);
+                       DEBUG(0,("vfs_chown_fsp: failed to get "
+                               "current working directory. Error was %s\n",
+                               strerror(errno)));
+                       return status;
+               }
+
+               if (!parent_dirname(talloc_tos(),
+                               fsp->fsp_name->base_name,
+                               &parent_dir,
+                               &final_component)) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               /* cd into the parent dir to pin it. */
+               ret = SMB_VFS_CHDIR(fsp->conn, parent_dir);
+               if (ret == -1) {
+                       return map_nt_error_from_unix(errno);
+               }
+
+               ZERO_STRUCT(local_fname);
+               local_fname.base_name = CONST_DISCARD(char *,final_component);
+
+               /* Must use lstat here. */
+               ret = SMB_VFS_LSTAT(fsp->conn, &local_fname);
+               if (ret == -1) {
+                       return map_nt_error_from_unix(errno);
+               }
+
+               /* Ensure it matches the fsp stat. */
+               if (!check_same_stat(&local_fname.st, &fsp->fsp_name->st)) {
+                        return NT_STATUS_ACCESS_DENIED;
+                }
+                path = final_component;
+        } else {
+                path = fsp->fsp_name->base_name;
+        }
+
+       if (fsp->posix_open || as_root) {
                ret = SMB_VFS_LCHOWN(fsp->conn,
-                       fsp->fsp_name->base_name,
+                       path,
                        uid, gid);
        } else {
                ret = SMB_VFS_CHOWN(fsp->conn,
-                       fsp->fsp_name->base_name,
+                       path,
                        uid, gid);
        }
+
        if (ret == 0) {
-               return NT_STATUS_OK;
+               status = NT_STATUS_OK;
+       } else {
+               status = map_nt_error_from_unix(errno);
+       }
+
+       if (as_root) {
+               vfs_ChDir(fsp->conn,saved_dir);
+               TALLOC_FREE(saved_dir);
+               TALLOC_FREE(parent_dir);
        }
-       return map_nt_error_from_unix(errno);
+       return status;
 }
 
 int smb_vfs_call_chdir(struct vfs_handle_struct *handle, const char *path)