首页
社区
课程
招聘
[原创]开发常识 | 彻底理清 CreateFile 读写权限与共享模式的关系
发表于: 2024-1-6 14:20 9550

[原创]开发常识 | 彻底理清 CreateFile 读写权限与共享模式的关系

2024-1-6 14:20
9550

前一阵子,我在编写文件变化监控程序的时候遇到了文件被占用的问题。很早之前写过一篇关于 CreateFile 函数的 dwDesiredAccessdwShareMode 参数的笔记。我发现之前的理解不够全面、准确。为了更好的理解这两个参数的作用,我搜索了大量资料,编写了测试程序及测试脚本,参考了 xp 源码,终于搞清楚这两个参数的作用。简而言之,需要遵循以下两个规则:

规则 1:后续的访问权限与先前的共享模式不能冲突。

规则 2:后续的共享模式与先前的访问权限不能冲突。

如果你对下面的几个问题有明确的答案并且清楚的知道原因,那么可以跳过本文了。

在总结之前,先看一下关键的权限检查代码。

说明: DesiredAccess 表示 访问权限,DesiredShareAccess 表示 共享模式。

代码中的注释已经写的很清楚了,再整体梳理一下:

更新逻辑(else if 分支):

每次权限检查成功后,如果指定了 Update 参数,SharedAccess->OpenCount 计数会加一。

DesiredAccess 包含 / / 删除标志的时候,SharedAccess->Readers / Writers / Deleters 计数会加一。

DesiredShareAccess 包含 / / 删除标志的时候,ShareAccess->SharedRead / SharedWrite / SharedDelete 计数会加一。

检查逻辑(if 分支):

如果本次调用时 DesiredAccess 包含了 / / 删除标志(FileObject->ReadAccess / WriteAccess / DeleteAccess 为真)并且在之前的调用中 DesiredShareAccess 缺少对应的 / / 删除标志(ShareAccess->SharedRead / SharedWrite / SharedDelete < ocount),违反规则 1,权限检查会失败。

如果在之前的调用中 DesiredAccess 包含了 / / 删除标志(ShareAccess->Readers / Writers / Deleters != 0),并且本次调用时 DesiredShareAccess 缺少对应 / / 删除标志(FileObject->SharedRead / SharedWrite / SharedDelete 为假),违反规则 2,权限检查会失败。

我把各种情况下的打开结果整理成了表格,供大家参考。

各项的意义解释如下:

访问权限 代表 dwDesiredAccess 参数,共享模式 代表 dwShareAccess 参数。1 表示第一次调用,2 表示第二次调用。

R Read,表示W Write,表示RW ReadWrite,表示读写N None, 表示独占

/ 表示或者。为了减少组合数量。比如第一行中的 访问权限 2 可以是 / / 读写中的任意一种。

--- 表示对应位置是什么都可以,不影响结果。比如,第一行的 访问权限 1 可以是 / / 读写中的任意一种,不论是哪种都会打开失败。

结果列只统计了第二次的结果,因为第一次总是成功的。

以上结论我在 win10 系统上亲自验证过,整体验证思路是用不同的参数调用 CreateFile 打开同一个文件。关键验证代码如下:

生成的程序名是 CreateFile.exe,该程序可以接收命令行参数,通过 -f 指定文件名,通过 -a 指定访问权限,通过 -s 指定共享模式, 通过 -h 显示帮助。

为了更方便的验证,我又写了批处理脚本,关键脚本如下:

脚本 CreateFileBatchCaller.bat 接收一个参数,内部会根据 - 分割参数,前四项有固定意义,分别表示第一次调用 CreateFile.exe 的访问权限和共享模式、第二次调用 CreateFile.exe 的访问权限和共享模式。

read-readwrite-write-none-failed.bat 是众多调用脚本中的一个,内部会把当前脚本的文件名(不包括扩展名)当作参数调用 CreateFileBatchCaller.bat 。 该脚本可以验证第一次以访问权限、读写共享模式打开文件,第二次以访问权限、独占共享模式打开文件的情况。

所有脚本及源码我已经上传到我的个人仓库了。如果你也想亲自动手验证一下,可以从如下位置获取测试代码,编译好的程序及测试脚本。

github:

ab9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6n7K9h3q4F1b7$3S2W2L8X3N6z5j5h3&6Q4x3V1k6y4P5f1u0D9L8$3N6e0N6s2g2X3k6W2)9J5c8Y4c8J5k6h3g2Q4x3V1k6E0j5i4y4@1k6i4u0Q4x3V1k6J5k6i4k6A6k6i4N6Q4x3X3c8o6M7X3g2S2N6r3g2r3K9h3I4W2i4K6u0V1c8r3g2K6K9i4u0W2b7h3y4U0k6i4y4K6i4K6u0V1f1$3S2S2M7X3g2y4L8$3c8W2

gitee:

211K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8W2k6g2)9J5k6h3y4G2L8g2)9J5c8X3u0A6j5h3&6U0K9r3g2F1k6$3&6S2L8W2)9J5c8X3#2&6i4K6u0V1j5X3I4G2k6#2)9J5k6s2y4@1N6h3k6X3i4K6u0r3N6s2u0W2k6g2)9J5c8X3#2S2M7%4c8W2M7W2)9J5c8Y4u0W2N6X3W2W2N6#2)9J5k6p5y4J5k6h3q4@1k6f1k6A6L8r3g2Q4x3X3c8p5k6i4y4A6M7X3g2m8j5$3y4W2M7%4y4Q4x3X3c8e0K9r3q4J5k6f1#2G2k6r3f1`.

百度云盘:

21cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Q4x3V1j5I4x3p5u0y4e0h3S2b7c8$3W2A6b7W2W2B7L8p5#2r3M7X3u0c8d9q4)9J5k6o6y4Y4i4K6y4r3M7s2N6V1i4K6y4p5N6r3W2T1L8b7`.`.

至此,文章开头的几个问题的答案应该已经很明显了。一起来看一下。

第一次尝试以访问权限,共享模式打开文件,会成功吗?

答:会成功。

第一次打开时总会成功。

如果第一次打开成功了,第二次尝试以访问权限,共享模式打开。会成功吗?

答:会成功。

第一次的共享模式是,第二次的访问权限是,第二次的访问权限与第一次的共享模式不冲突。

第二次的共享模式是,第一次的访问权限是,第二次的共享模式与第一次的访问权限不冲突。

open-result-2

如果第二次打开成功了,第三次尝试以//读写访问权限,读写共享模式打开,会成功吗?

答:不会成功。

第三次的访问权限是的话,与第一次的共享模式()冲突。

第三次的访问权限是的话,与第二次的共享模式()冲突。

第三次的访问权限是读写的话,既与第一次的共享模式()冲突,又与第二次的共享模式()冲突。
open-result-3

这里只贴了第三次的访问权限是的情况,其它两种情况也会失败。

第一次尝试以访问权限,共享模式打开文件,第二次尝试以访问权限,读写共享模式打开。第三次尝试以访问权限,读写共享模式打开,会成功吗?

答:会成功。

第三次的访问权限(),既不与第一次的共享模式()冲突,又不与第二次的共享模式(读写)冲突。

第三次的共享模式(读写),既不与第一次的访问权限()冲突,又不与第二次的访问权限()冲突。
open-result-4

最后,贴一下之前整理的笔记,基本正确,但是不够全面,不够深刻。

一直对 CreateFile 的参数 dwDesiredAccessdwShareMode 的具体作用不是很清楚,今天重读《windows 核心编程》的时候有了一些新感悟。 简要总结如下:

dwDesiredAccess 各种值及含义抄录如下(摘自 《Windows核心编程》第 5 版 第10p279):

dwShareMode 的各种值及含义抄录如下(摘自 《Windows核心编程》第 5 版 第10p279):

友情提示: 上表中的 如果设备已经以 xxx 方式打开 指的是先前调用的 dwShareMode 参数。

NTSTATUS IoCheckShareAccess(
  IN ACCESS_MASK DesiredAccess,
  IN ULONG DesiredShareAccess,
  IN OUT PFILE_OBJECT FileObject,
  IN OUT PSHARE_ACCESS ShareAccess,
  IN BOOLEAN Update
)
{
  PAGED_CODE();
   
  // 获取本次调用时,指定的 读/写/删除 访问权限标志
  FileObject->ReadAccess = (BOOLEAN) ((DesiredAccess & (FILE_EXECUTE | FILE_READ_DATA)) != 0);
  FileObject->WriteAccess = (BOOLEAN) ((DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0);
  FileObject->DeleteAccess = (BOOLEAN) ((DesiredAccess & DELETE) != 0);
   
  if (FileObject->ReadAccess || FileObject->WriteAccess || FileObject->DeleteAccess)
  {     
    // 获取本次调用时,指定的 读/写/删除 共享模式标志
    FileObject->SharedRead = (BOOLEAN) ((DesiredShareAccess & FILE_SHARE_READ) != 0);
    FileObject->SharedWrite = (BOOLEAN) ((DesiredShareAccess & FILE_SHARE_WRITE) != 0);
    FileObject->SharedDelete = (BOOLEAN) ((DesiredShareAccess & FILE_SHARE_DELETE) != 0);
   
    if (FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION)
    {
      PIOP_FILE_OBJECT_EXTENSION  fileObjectExtension =(PIOP_FILE_OBJECT_EXTENSION)(FileObject + 1);
      if (fileObjectExtension->FileObjectExtensionFlags & FO_EXTENSION_IGNORE_SHARE_ACCESS_CHECK)
        return STATUS_SUCCESS;
    }
   
    ULONG ocount = ShareAccess->OpenCount;
   
    if ( // 本次调用时 DesiredAccess 包含了读/写/删除标志,并且
         // 在之前的调用中,DesiredShareAccess 缺少对应的读/写/删除标志(ShareXXX < ocount)
         (FileObject->ReadAccess && (ShareAccess->SharedRead < ocount))
      || (FileObject->WriteAccess && (ShareAccess->SharedWrite < ocount))
      || (FileObject->DeleteAccess && (ShareAccess->SharedDelete < ocount))
         // 之前的调用中 DesiredAccess 包含了读/写/删除标志,并且
         // 本次调用时 DesiredShareAccess 缺少对应读/写/删除标志
      || ((ShareAccess->Readers != 0) && !FileObject->SharedRead)
      || ((ShareAccess->Writers != 0) && !FileObject->SharedWrite)
      || ((ShareAccess->Deleters != 0) && !FileObject->SharedDelete)
    )
    {
      return STATUS_SHARING_VIOLATION;
    }
    else if (Update)
    {
      ShareAccess->OpenCount++; // 每次权限检查通过后,打开计数 +1
       
      // 本次调用时 DesiredAccess 包含了读/写/删除标志,对应的计数 +1
      ShareAccess->Readers += FileObject->ReadAccess;
      ShareAccess->Writers += FileObject->WriteAccess;
      ShareAccess->Deleters += FileObject->DeleteAccess;
       
      // 本次调用时 DesiredShareAccess 包含了读/写/删除标志,对应的计数 +1
      ShareAccess->SharedRead += FileObject->SharedRead;
      ShareAccess->SharedWrite += FileObject->SharedWrite;
      ShareAccess->SharedDelete += FileObject->SharedDelete;
    }
  }
  return STATUS_SUCCESS;
}
NTSTATUS IoCheckShareAccess(
  IN ACCESS_MASK DesiredAccess,
  IN ULONG DesiredShareAccess,
  IN OUT PFILE_OBJECT FileObject,
  IN OUT PSHARE_ACCESS ShareAccess,
  IN BOOLEAN Update
)
{
  PAGED_CODE();
   
  // 获取本次调用时,指定的 读/写/删除 访问权限标志
  FileObject->ReadAccess = (BOOLEAN) ((DesiredAccess & (FILE_EXECUTE | FILE_READ_DATA)) != 0);
  FileObject->WriteAccess = (BOOLEAN) ((DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0);
  FileObject->DeleteAccess = (BOOLEAN) ((DesiredAccess & DELETE) != 0);
   
  if (FileObject->ReadAccess || FileObject->WriteAccess || FileObject->DeleteAccess)
  {     
    // 获取本次调用时,指定的 读/写/删除 共享模式标志
    FileObject->SharedRead = (BOOLEAN) ((DesiredShareAccess & FILE_SHARE_READ) != 0);
    FileObject->SharedWrite = (BOOLEAN) ((DesiredShareAccess & FILE_SHARE_WRITE) != 0);
    FileObject->SharedDelete = (BOOLEAN) ((DesiredShareAccess & FILE_SHARE_DELETE) != 0);
   
    if (FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION)
    {
      PIOP_FILE_OBJECT_EXTENSION  fileObjectExtension =(PIOP_FILE_OBJECT_EXTENSION)(FileObject + 1);
      if (fileObjectExtension->FileObjectExtensionFlags & FO_EXTENSION_IGNORE_SHARE_ACCESS_CHECK)
        return STATUS_SUCCESS;
    }
   
    ULONG ocount = ShareAccess->OpenCount;
   
    if ( // 本次调用时 DesiredAccess 包含了读/写/删除标志,并且
         // 在之前的调用中,DesiredShareAccess 缺少对应的读/写/删除标志(ShareXXX < ocount)
         (FileObject->ReadAccess && (ShareAccess->SharedRead < ocount))
      || (FileObject->WriteAccess && (ShareAccess->SharedWrite < ocount))
      || (FileObject->DeleteAccess && (ShareAccess->SharedDelete < ocount))
         // 之前的调用中 DesiredAccess 包含了读/写/删除标志,并且
         // 本次调用时 DesiredShareAccess 缺少对应读/写/删除标志
      || ((ShareAccess->Readers != 0) && !FileObject->SharedRead)
      || ((ShareAccess->Writers != 0) && !FileObject->SharedWrite)
      || ((ShareAccess->Deleters != 0) && !FileObject->SharedDelete)
    )
    {
      return STATUS_SHARING_VIOLATION;
    }
    else if (Update)
    {
      ShareAccess->OpenCount++; // 每次权限检查通过后,打开计数 +1
       
      // 本次调用时 DesiredAccess 包含了读/写/删除标志,对应的计数 +1
      ShareAccess->Readers += FileObject->ReadAccess;
      ShareAccess->Writers += FileObject->WriteAccess;
      ShareAccess->Deleters += FileObject->DeleteAccess;
       
      // 本次调用时 DesiredShareAccess 包含了读/写/删除标志,对应的计数 +1
      ShareAccess->SharedRead += FileObject->SharedRead;
      ShareAccess->SharedWrite += FileObject->SharedWrite;
      ShareAccess->SharedDelete += FileObject->SharedDelete;
    }
  }
  return STATUS_SUCCESS;
}
访问权限 1 共享模式 1 访问权限 2 共享模式 2 结果 说明
--- N R / W / RW --- 失败 违反了 规则1
--- R W / RW --- 失败 违反了 规则1
--- W R / RW --- 失败 违反了 规则1
R / W / RW --- --- N 失败 违反了 规则2
W / RW --- --- R 失败 违反了 规则2
R / RW --- --- W 失败 违反了 规则2
R R R R / RW 成功 第二次的访问权限与第一次的共享模式不冲突。 <br/>第二次的共享模式与第一次的访问权限不冲突。
R W W R / RW 成功 同上
R RW R / W / RW R / RW 成功 同上
W W W W / RW 成功 同上
W R R W / RW 成功 同上
W RW R / W / RW W / RW 成功 同上
RW R R RW 成功 同上
RW W W RW 成功 同上
RW RW R / W / RW RW 成功 同上
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace CreateFile
{
    class Program
    {
        // ref 115K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8X3g2F1i4K6u0V1N6i4y4Q4x3V1k6V1L8%4c8F1k6i4c8Q4x3V1k6K6N6r3q4F1k6r3q4J5k6q4)9J5c8X3y4G2L8h3#2S2L8X3c8D9K9h3&6W2i4K6u0r3k6r3g2X3K9h3&6W2i4K6u0V1j5$3!0E0L8h3q4F1k6s2x3`.
        static Command SetupCommandHandler()
        {
            var filePathOption = new Option<string>(name: "--path", getDefaultValue: () => "test.txt", description: "file path");
            filePathOption.AddAlias("-f");
            filePathOption.AddAlias("-p");
 
            var fileModeOption = new Option<string>(name: "--mode", getDefaultValue: () => "Open", description: "file mode")
                .FromAmong("CreateNew", "Create", "Open", "OpenOrCreate", "Truncate", "Append");
            fileModeOption.AddAlias("-m");
 
            var fileShareOption = new Option<string>("--share", "file share") { IsRequired = true }
                .FromAmong("None", "Read", "Write", "ReadWrite", "Delete", "Inheritable");
            fileShareOption.AddAlias("-s");
 
            var fileAccessOption = new Option<string>("--access", "file access") { IsRequired = true }
                .FromAmong("Read", "Write", "ReadWrite");
            fileAccessOption.AddAlias("-a");
 
            var autoQuitOption = new Option<bool>(name: "--autoquit", getDefaultValue: () => false, description: "auto quit");
            autoQuitOption.AddAlias("-q");
 
            var command = new RootCommand();
            command.Add(filePathOption);
            command.Add(fileModeOption);
            command.Add(fileShareOption);
            command.Add(fileAccessOption);
            command.Add(autoQuitOption);
 
            command.SetHandler((filePath, fileMode, fileShare, fileAccess, autoQuit) =>
            {
                OpenFileAndWait(filePath, fileMode, fileShare, fileAccess, autoQuit);
            }, filePathOption, fileModeOption, fileShareOption, fileAccessOption, autoQuitOption);
 
            return command;
        }
 
        static void Main(string[] args)
        {
            var command = SetupCommandHandler();
            command.Invoke(args);
        }
 
        static void OpenFileAndWait(string strFilePath, string strFileMode, string strFileShare, string strFileAccess, bool autoQuit)
        {
            FileStream stream = null;
            try
            {
                var fileMode = (FileMode)System.Enum.Parse(typeof(FileMode), strFileMode);
                var fileShare = (FileShare)System.Enum.Parse(typeof(FileShare), strFileShare);
                var fileAccess = (FileAccess)System.Enum.Parse(typeof(FileAccess), strFileAccess);
 
                System.Console.WriteLine(string.Format("[{0}] file:{1}, mode: {2}, share: {3}, access: {4}!"
                  , System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), strFilePath, strFileMode, strFileShare, strFileAccess));
 
                stream = File.Open(strFilePath, fileMode, fileAccess, fileShare);
            }
            catch (Exception ex)
            {
                System.Console.WriteLine(string.Format("opening file [{0}] failed with {1}!", strFilePath, ex));
            }
 
            if (!autoQuit)
            {
                System.Console.WriteLine("press any key to continue...");
                System.Console.ReadKey();
            }
 
            if (stream != null)
            {
                stream.Dispose();
            }
        }
    }
}
using System;
using System.Collections.Generic;

[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 7
支持
分享
最新回复 (6)
雪    币: 39197
活跃值: (7505)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
有时还有个x代表什么意思?
2024-1-6 17:28
0
雪    币: 5531
活跃值: (31866)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2024-1-6 23:08
1
雪    币: 8519
活跃值: (9142)
能力值: ( LV12,RANK:360 )
在线值:
发帖
回帖
粉丝
4
ninebell 有时还有个x代表什么意思?
没有 x 啊?没太理解您的意思
2024-1-7 18:37
0
雪    币: 202
活跃值: (171)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
排版一目了然,很赞
2024-1-8 01:06
0
雪    币: 39197
活跃值: (7505)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
编程难 没有 x 啊?没太理解您的意思
打开x64dbg,Alt+M内存窗口就看到了。
Only Read
Write
Execute
这些能拼出来的意思肯定直接就知道了,还有个X呢?
2024-1-8 08:46
0
雪    币: 3
活跃值: (466)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
感谢分享
2024-3-5 11:33
0
游客
登录 | 注册 方可回帖
返回