File.Exists acts differently when access is denied to the file vs denied to the dir

℡╲_俬逩灬. 提交于 2020-08-19 07:45:05

问题


Based on the MSDN documentation of File.Exists, the File.Exists method should return false on any error, including the caller not having access to read the file.

I would expect it to return false both when the file is set to FullControl denied to the user and FullControl denied to the user to the directory the file lives in.

What I'm seeing is when the user has access to the directory, but not the file, File.Exists returns true; however, if the user has no access to the directory, File.Exists returns false.

I wrote a small program that demonstrates what I'm talking about:

using System;
using System.DirectoryServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;

namespace ConsoleApplication1
{
    internal class Program
    {
        private const string DirName = "TestDir";
        private const string FileName = "File.txt";
        private const string Password = "Password1";
        private const string UserName = "PermissionTestUser";
        private static WindowsImpersonationContext Identity = null;
        private static IntPtr LogonToken = IntPtr.Zero;

        public enum LogonProvider
        {
            LOGON32_PROVIDER_DEFAULT = 0,
            LOGON32_PROVIDER_WINNT35 = 1,
            LOGON32_PROVIDER_WINNT40 = 2,
            LOGON32_PROVIDER_WINNT50 = 3
        };

        public enum LogonType
        {
            LOGON32_LOGON_INTERACTIVE = 2,
            LOGON32_LOGON_NETWORK = 3,
            LOGON32_LOGON_BATCH = 4,
            LOGON32_LOGON_SERVICE = 5,
            LOGON32_LOGON_UNLOCK = 7,
            LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
            LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
        };

        public static void Main(string[] args)
        {
            string filePath = Path.Combine(DirName, FileName);
            try
            {
                CreateUser();
                CreateDir();
                CreateFile(filePath);

                // grant user full control to the dir
                SetAccess(DirName, AccessControlType.Allow);
                // deny user full control to the file
                SetAccess(filePath, AccessControlType.Deny);

                // impersonate user
                Impersonate();
                Console.WriteLine("File.Exists (with dir permissions): {0}", File.Exists(filePath));
                UndoImpersonate();

                // deny access to dir
                SetAccess(DirName, AccessControlType.Deny);

                // impersonate user
                Impersonate();
                Console.WriteLine("File.Exists (without dir permissions): {0}", File.Exists(filePath));
                UndoImpersonate();
            }
            finally
            {
                UndoImpersonate();
                DeleteDir();
                DeleteUser();
            }
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool CloseHandle(IntPtr handle);

        private static void CreateDir()
        {
            Directory.CreateDirectory(DirName);
        }

        private static void CreateFile(string path)
        {
            File.Create(path).Dispose();
        }

        private static void CreateUser()
        {
            DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
            DirectoryEntry newUser = ad.Children.Add(UserName, "user");
            newUser.Invoke("SetPassword", new object[] { Password });
            newUser.Invoke("Put", new object[] { "Description", "Test user" });
            newUser.CommitChanges();
        }

        private static void DeleteDir()
        {
            Directory.Delete(DirName, true);
        }

        private static void DeleteUser()
        {
            DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
            DirectoryEntries users = ad.Children;
            DirectoryEntry user = users.Find(UserName, "user");

            if (user != null)
            {
                users.Remove(user);
            }
        }

        private static void Impersonate()
        {
            if (LogonUser(UserName, ".", Password, (int)LogonType.LOGON32_LOGON_INTERACTIVE, (int)LogonProvider.LOGON32_PROVIDER_DEFAULT, ref LogonToken))
            {
                Identity = WindowsIdentity.Impersonate(LogonToken);
                return;
            }
        }

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool LogonUser(string lpszUserName,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);

        private static void SetAccess(string path, AccessControlType type)
        {
            FileSecurity fs = File.GetAccessControl(path);
            FileSystemAccessRule far = new FileSystemAccessRule(UserName, FileSystemRights.FullControl, type);
            fs.AddAccessRule(far);
            File.SetAccessControl(path, fs);
        }

        private static void UndoImpersonate()
        {
            if (Identity != null)
            {
                Identity.Undo();
                Identity = null;
            }

            if (LogonToken != IntPtr.Zero)
            {
                CloseHandle(LogonToken);
                LogonToken = IntPtr.Zero;
            }
        }
    }
}

The result of running this program is:

File.Exists (with dir permissions): True
File.Exists (without dir permissions): False

Can anyone explain why they differ? In both instances, the user doesn't have read access to the file.


回答1:


That is the default behavior of the File.Exist. According to MSDN:

File.Exist

Return Value Type: System.Boolean

true if the caller has the required permissions and path contains the name of an existing file; otherwise, false. This method also returns false if path is null, an invalid path, or a zero-length string. If the caller does not have sufficient permissions to read the specified file, no exception is thrown and the method returns false regardless of the existence of path.

And additionally

The Exists method should not be used for path validation, this method merely checks if the file specified in path exists. Passing an invalid path to Exists returns false.

In other words, the required permission here, is the required permission to know the existence of the file (as the method name implies, File.Exist). And this means that as long as a user has access to the directory, it can know if the file exists or not.

Whether the user has file access or not doesn't affect the user's knowledge of the existence of the file, given the directory permission. But without directory permission, a user cannot know the existence of the file, and thus File.Exist returns false


Edit (after feedback from comments):

And probably the rather confusing part would be the last sentence:

If the caller does not have sufficient permissions to read the specified file, no exception is thrown and the method returns false regardless of the existence of path.

The sufficient permissions to read the specified file is depending on the read-access of the parent directory rather than read-access of the specified file. (Additional comment by Mr. Rob). The word "sufficient" may give some hint about the behavior that it will only depend on read-access to the parent directory is needed, not the read-access to the specified file.

But I admit that the explanation and the choice of word may sound rather counter-intuitive as people may intuitively interpret "sufficient permissions to read the specified file" as the read-access to the specified file rather than to the parent directory.



来源:https://stackoverflow.com/questions/34890013/file-exists-acts-differently-when-access-is-denied-to-the-file-vs-denied-to-the

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!