Opcode

Opcode

低级策略(low-level-policy)是使用策略“操作码”的概念实现的。操作码是一种包含足够信息的结构,可以对单个输入参数执行一次比较,下面给出了几个比较的范例:

  • Is input parameter 3 not equal to nullptr?
  • Does input parameter 2 start with L”c:\“?
  • Is input parameter 5, bit 3 is equal 1?

每个操作码实际上相当于一个函数调用,但操作码知道原函数的参数的个数是 原函数的参数个数-1

1
2
3
4
5
6
7
8
9
10
11
bool fn(a, b, c, d)  with 4 arguments
Then an opcode is: op(fn, b, c, d)

关于操作码评估:
op.eval(a) ------------------------> fn(a,b,c,d)
internally calls

评估是在具有N个比较操作码加1个动作操作码的操作码组中进行的。

[comparison 1][comparison 2]...[comparison N][action][comparison 1]...
----- evaluation order----------->

每个操作码组编码一个高级策略规则。仅当组上的所有条件计算为true时,该规则才适用。操作操作码包含该特定规则的策略结果。

关于 opcode:

  • 每个 opcode 都有其对应的Options,这些Options在创建 opcode 的时候就已经被指定。
  • 每个 opcode 由:opcodeID、 一个index索引表示哪个是输入参数、一个参数数组三部分组成。
  • opcode 由 borker 进程中生成的,并作为原始内存复制到目标进程
  • 一个opcode组由N个comparison opcodes加上一个action opcode组成

OpcodeID

以下是已实现的操作码。

  • OP_ALWAYS_FALSE: Evaluates to false (EVAL_FALSE).

  • OP_ALWAYS_TRUE: Evaluates to true (EVAL_TRUE).

  • OP_NUMBER_MATCH:Match a 32-bit integer as n == a.

  • OP_NUMBER_MATCH_RANGE:Match a 32-bit integer as a <= n <= b.

  • OP_NUMBER_AND_MATCH:Match using bitwise AND; as in: n & a != 0.

  • OP_WSTRING_MATCH:Match a string for equality.

  • OP_ACTION:Evaluates to an action opcode.

Opcode Options

适用于每个操作码的选项。它们在使用OpcodeFactory::MakeOpXXXXX()函数族创建每个操作码时指定。

  • const uint32_t kPolNone = 0 :无特殊含义。
  • const uint32_t kPolNegateEval = 1:EVAL_TRUE转换为EVAL_FALSE,反之亦然。这允许表示否定条件,例如if(a&&!b)
  • const uint32_t kPolClearContext = 2:MatchContext结构归零。这发生在操作码被evaluated之后。
  • const uint32_t kPolUseOREval = 4:在评估这组操作码时使用 OR。默认情况下,策略评估器在评估时使用 AND。与 kPolNegateEval 一起使用时非常有用。例如使用这个标志可以把 if(! (a && b && c)) 表示为if ((!a) || (!b) || (!c))

Comparison opcode

  • EVAL_TRUE: Opcode condition evaluated true.

  • EVAL_FALSE: Opcode condition evaluated false.

  • EVAL_ERROR:Opcode condition generated an error while evaluating.

Action opcode

  • ASK_BROKER:target 必须向 broker 生成一个IPC。在浏览器端,这意味着授予访问权限。
  • DENY_ACCESS:没有授予对资源的访问权限。
  • GIVE_READONLY:授予对资源的只读访问权限
  • GIVE_ALLACCESS:授予对资源的完全访问权限。
  • GIVE_CACHED:不需要 IPC。 target可以返回缓存的句柄。
  • GIVE_FIRST:TODO(cpu)
  • SIGNAL_ALARM: 不寻常的活动。 产生警报。
  • FAKE_SUCCESS:不调用原始函数。 只需返回’成功’。
  • FAKE_ACCESS_DENIED:不调用原始函数。 只需返回“拒绝”,不进行 IPC通信。
  • TERMINATE_PROCESS:销毁target进程。 IPC通信。

Policyopcde

policy opcode是基于此运营的,除了第一个参数都存储在此对象中进行裁决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//chromium/sandbox/win/src/policy_engine_opcodes.h
class PolicyOpcode {
friend class OpcodeFactory;

public:
// 评估函数,根据opcode是comparison还是action,返回两类结果
// parameters: 一个存储输入参数的数组
// count: 第一个参数传递的参数的数量。
// match: 在操作码求值序列中保留的匹配上下文。
EvalResult Evaluate(const ParameterSet* parameters,
size_t count,
MatchContext* match);


// Valid index values are from 0 to < kArgumentCount.
// 根据 index,返回参数
template <typename T>
void GetArgument(size_t index, T* argument) const {
static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size");
*argument = *reinterpret_cast<const T*>(&arguments_[index].mem);
}

// Sets a stored argument by index.
template <typename T>
void SetArgument(size_t index, const T& argument) {
static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size");
*reinterpret_cast<T*>(&arguments_[index].mem) = argument;
}

// 检索字符串参数的实际地址
const wchar_t* GetRelativeString(size_t index) const {
ptrdiff_t str_delta = 0;
GetArgument(index, &str_delta);
// 字符串的GetArgument返回的是一个offset
const char* delta = reinterpret_cast<const char*>(this) + str_delta;
return reinterpret_cast<const wchar_t*>(delta);
}

// Returns true if this opcode is an action opcode without actually
// evaluating it. Used to do a quick scan forward to the next opcode group.
bool IsAction() const { return (OP_ACTION == opcode_id_); }

// Returns the opcode type.
OpcodeID GetID() const { return opcode_id_; }

// Returns the stored options such as kPolNegateEval and others.
uint32_t GetOptions() const { return options_; }

// Sets the stored options such as kPolNegateEval.
void SetOptions(uint32_t options) { options_ = options; }

// Returns the parameter of the function the opcode concerns.
uint16_t GetParameter() const { return parameter_; }

private:
static const size_t kArgumentCount = 4; // The number of supported argument.

struct OpcodeArgument {
UINT_PTR mem;
};
// Better define placement new in the class instead of relying on the
// global definition which seems to be fubared.
void* operator new(size_t, void* location) { return location; }

// 真正去评估的函数
EvalResult EvaluateHelper(const ParameterSet* parameters,
MatchContext* match);
OpcodeID opcode_id_;
int16_t parameter_;
uint32_t options_;
OpcodeArgument arguments_[PolicyOpcode::kArgumentCount];
};

PolicyOpcode::Evaluate()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//chromium/sandbox/win/src/policy_engine_opcodes.cc
//此函数是评估任何操作码的唯一入口。
EvalResult PolicyOpcode::Evaluate(const ParameterSet* call_params,
size_t param_count,
MatchContext* match) {
if (!call_params)
return EVAL_ERROR;
const ParameterSet* selected_param = nullptr;
if (parameter_ >= 0) {
if (static_cast<size_t>(parameter_) >= param_count) {
return EVAL_ERROR;
}
selected_param = &call_params[parameter_];
}
//selected_param借助parameter_的值找到本次想要处理的ParameterSet
EvalResult result = EvaluateHelper(selected_param, match);

// 不管操作码的具体类型是什么,都应用通用选项。
if (kPolNone == options_) {
return result;
}
// 如果本PolicyOpcode的标记了kPolNegateEval位,那么就要对结果取反(ERROR不管)
if (options_ & kPolNegateEval) {
if (EVAL_TRUE == result) {
result = EVAL_FALSE;
} else if (EVAL_FALSE == result) {
result = EVAL_TRUE;
} else if (EVAL_ERROR != result) {
result = EVAL_ERROR;
}
}
if (match) {
// 如果标记了kPolClearContext位,那么要对辅助结构MatchContext进行清理工作
if (options_ & kPolClearContext)
match->Clear();
// 如果标记了kPolUseOREval,那么就对辅助结构MatchContext打上标记
if (options_ & kPolUseOREval)
match->options = kPolUseOREval;//默认是用AND来裁决,该标记表示用OR
}
return result;
}

#define OPCODE_EVAL(op, x, y, z) \
case op: \
return OpcodeEval<op>(x, y, z)

EvalResult PolicyOpcode::EvaluateHelper(const ParameterSet* parameters,
MatchContext* match) {
switch (opcode_id_) {
OPCODE_EVAL(OP_ALWAYS_FALSE, this, parameters, match);
OPCODE_EVAL(OP_ALWAYS_TRUE, this, parameters, match);
OPCODE_EVAL(OP_NUMBER_MATCH, this, parameters, match);
OPCODE_EVAL(OP_NUMBER_MATCH_RANGE, this, parameters, match);
OPCODE_EVAL(OP_NUMBER_AND_MATCH, this, parameters, match);
OPCODE_EVAL(OP_WSTRING_MATCH, this, parameters, match);
OPCODE_EVAL(OP_ACTION, this, parameters, match);
default:
return EVAL_ERROR;
}
}

EvaluateHelper 根据opcode_id_来分类调用OPCODE_EVAL来评估

OPCODE_EVAL()

每个opcodeid 都有其对应的OpcodeEval实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//chromium/sandbox/win/src/policy_engine_opcodes.cc
template <>
EvalResult OpcodeEval<OP_ALWAYS_FALSE>(PolicyOpcode* opcode,
const ParameterSet* param,
MatchContext* context) {
return EVAL_FALSE;
}
template <>
EvalResult OpcodeEval<OP_ALWAYS_TRUE>(PolicyOpcode* opcode,
const ParameterSet* param,
MatchContext* context) {
return EVAL_TRUE;
}
template <>
EvalResult OpcodeEval<OP_ACTION>(PolicyOpcode* opcode,
const ParameterSet* param,
MatchContext* context) {
int action = 0;
opcode->GetArgument(0, &action);
return static_cast<EvalResult>(action);
}
template <>
EvalResult OpcodeEval<OP_NUMBER_AND_MATCH>(PolicyOpcode* opcode,
const ParameterSet* param,
MatchContext* context) {
uint32_t value = 0;
if (!param->Get(&value))
return EVAL_ERROR;

uint32_t number = 0;
opcode->GetArgument(0, &number);
return (number & value) ? EVAL_TRUE : EVAL_FALSE;
}
...
...

如果是 match 类的 ID,其最终就是 opcode内存储的Argumentparam的比较,返回 TRUE or FALSE

如果是 OP_ACTION,则返回其对应的EvalResult,如:askbroker.

OpcodeFactory

opcode 的一个工程类,用来make opcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//chromium/sandbox/win/src/policy_engine_opcodes.h
// Factory通过使用构造函数中给定的内存块创建操作码来工作。
//操作码本身是从内存的开头(顶部)分配的,而操作码需要的任何字符串都是从内存的结尾(底部)分配的。
// In essence:
// low address ---> [opcode 1]
// [opcode 2]
// [opcode 3]
// | | <--- memory_top_
// | free |
// | |
// | | <--- memory_bottom_
// [string 1]
// high address --> [string 2]
//
class OpcodeFactory {
public:
// memory: 指向创建操作码的内存块的基指针。
// memory_size: chunk size
OpcodeFactory(char* memory, size_t memory_size) : memory_top_(memory) {
memory_bottom_ = &memory_top_[memory_size];
}
// policy:包含创建操作码的原始内存
OpcodeFactory(PolicyBuffer* policy, size_t memory_size) {
memory_top_ = reinterpret_cast<char*>(&policy->opcodes[0]);
memory_bottom_ = &memory_top_[memory_size];
}

size_t memory_size() const {
DCHECK_GE(memory_bottom_, memory_top_);
return memory_bottom_ - memory_top_;
}

// Creates an OpAlwaysFalse opcode.
PolicyOpcode* MakeOpAlwaysFalse(uint32_t options);
PolicyOpcode* MakeOpAlwaysTrue(uint32_t options);
PolicyOpcode* MakeOpAction(EvalResult action, uint32_t options);
PolicyOpcode* MakeOpNumberMatch(int16_t selected_param,
uint32_t match,
uint32_t options);
PolicyOpcode* MakeOpVoidPtrMatch(int16_t selected_param,
const void* match,
uint32_t options);
PolicyOpcode* MakeOpNumberMatchRange(int16_t selected_param,
uint32_t lower_bound,
uint32_t upper_bound,
uint32_t options);
PolicyOpcode* MakeOpWStringMatch(int16_t selected_param,
const wchar_t* match_str,
int start_position,
StringMatchOptions match_opts,
uint32_t options);
PolicyOpcode* MakeOpNumberAndMatch(int16_t selected_param,
uint32_t match,
uint32_t options);

private:
PolicyOpcode* MakeBase(OpcodeID opcode_id,
uint32_t options,
int16_t selected_param);
ptrdiff_t AllocRelative(void* start, const wchar_t* str, size_t length);
char* memory_top_;
char* memory_bottom_;
DISALLOW_COPY_AND_ASSIGN(OpcodeFactory);
};

其中比较重要的是MakeBase(),所有的 make,最后都会调用这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PolicyOpcode* OpcodeFactory::MakeBase(OpcodeID opcode_id,
uint32_t options,
int16_t selected_param) {
if (memory_size() < sizeof(PolicyOpcode))
return nullptr;

// opcode从top开始向下占用buffer
PolicyOpcode* opcode = new (memory_top_) PolicyOpcode();

// Fill in the standard fields, that every opcode has.
memory_top_ += sizeof(PolicyOpcode);
opcode->opcode_id_ = opcode_id; // 标记 opcode type
opcode->SetOptions(options); // 标记 opcode options
// 传入的selected_param表示用于和该PolicyOpcode比较的参数在ParameterSet中是第几个,也就是索引
opcode->parameter_ = selected_param;
return opcode;
}

Filesystem

Target在执行5个文件系统相关的API时,会因为Interceptions的部署(该子系统由broker远程部署(service call))而调用hook函数,hook函数通过SharedMem IPC与broker通信,将调用参数传给broker,broker此后通过dispatcher分发找到对应子系统的dispatcher,子系统dispatcher会匹配调用参数并调用一早便注册好的callback,callback会进行Low-level policy的Evaluate鉴权,并进一步调用low-level policy的业务处理函数,根据鉴权结果来执行API,并在CrossCallResult回执结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//chromium/sandbox/win/src/filesystem_policy.h
class FileSystemPolicy {
public:
// 创建所需的低级策略规则来评估文件 IO 的高级策略规则,尤其是文件打开或创建操作。
// 'name' 是文件名或目录名。 “语义”。
// 'semantics' 是打开或创建所需的语义
//'policy' 是将要添加规则的策略生成器。
static bool GenerateRules(const wchar_t* name,
TargetPolicy::Semantics semantics,
LowLevelPolicy* policy);

// Add basic file system rules.
static bool SetInitialRules(LowLevelPolicy* policy);

// IPC 收到 creat file 请求时的操作
// 'client_info' :发出请求的目标进程。
// 'eval_result' : 要完成的预期策略操作。
// 'file' : 目标文件或目录。
static bool CreateFileAction(EvalResult eval_result,
const ClientInfo& client_info,
const std::wstring& file,
uint32_t attributes,
uint32_t desired_access,
uint32_t file_attributes,
uint32_t share_access,
uint32_t create_disposition,
uint32_t create_options,
HANDLE* handle,
NTSTATUS* nt_status,
ULONG_PTR* io_information);

// IPC 收到 open file 请求时的操作
static bool OpenFileAction(EvalResult eval_result,
const ClientInfo& client_info,
const std::wstring& file,
uint32_t attributes,
uint32_t desired_access,
uint32_t share_access,
uint32_t open_options,
HANDLE* handle,
NTSTATUS* nt_status,
ULONG_PTR* io_information);

// IPC 收到 Query 请求时的操作
static bool QueryAttributesFileAction(EvalResult eval_result,
const ClientInfo& client_info,
const std::wstring& file,
uint32_t attributes,
FILE_BASIC_INFORMATION* file_info,
NTSTATUS* nt_status);

// IPC 收到 Query full请求时的操作
static bool QueryFullAttributesFileAction(
EvalResult eval_result,
const ClientInfo& client_info,
const std::wstring& file,
uint32_t attributes,
FILE_NETWORK_OPEN_INFORMATION* file_info,
NTSTATUS* nt_status);

// // IPC收到的set_info 请求时的操作
static bool SetInformationFileAction(EvalResult eval_result,
const ClientInfo& client_info,
HANDLE target_file_handle,
void* file_info,
uint32_t length,
uint32_t info_class,
IO_STATUS_BLOCK* io_block,
NTSTATUS* nt_status);
};

chrome的Windows版Sandbox是利用系统的

  • user32!CreateDesktopW

  • kernel32!CreateJobObjectW

  • advapi32!CreateRestrictedToken

  • advapi32!CreateProcessAsUserW

  • advapi32!SetThreadToken

  • advapi32!RevertToSelf

这些API为每个网页单独建立一个权限受限的renderer子进程专门用来解析网页, 如果网页有什么越轨的行为就销毁这个renderer子进程. 因为renderer子进程权限太低无法进行诸如下载上传文件这些操作, chrome改写了renderer子进程10个API, 如果参数合乎policy要求就让正常权限的browser主进程代为完成。

  • ntdll!NtCreateFile

  • ntdll!NtOpenFile

  • ntdll!NtQueryAttributesFile

  • ntdll!NtQueryFullAttributesFile

  • ntdll!NtSetInformationFile

  • ntdll!NtOpenThread

  • ntdll!NtOpenProcess

  • ntdll!NtOpenProcessToken

  • ntdll!NtSetInformationThread

  • ntdll!NtOpenThreadToken

FileSystemPolicy::GenerateRules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//chromium/sandbox/win/src/filesystem_policy.cc
bool FileSystemPolicy::GenerateRules(const wchar_t* name,
TargetPolicy::Semantics semantics,
LowLevelPolicy* policy) {
std::wstring mod_name(name);
if (mod_name.empty()) {
return false;
}
//处理 mod_name。
if (!PreProcessName(&mod_name)) {
// The path to be added might contain a reparse point.
NOTREACHED();
return false;
}

// TODO(cpu) bug 32224: This prefix add is a hack because we don't have the
// infrastructure to normalize names. In any case we need to escape the
// question marks.
if (_wcsnicmp(mod_name.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) {
mod_name = FixNTPrefixForMatch(mod_name);
name = mod_name.c_str();
}

// 设定 ASK_broker
EvalResult result = ASK_BROKER;

// List of supported calls for the filesystem.
const unsigned kCallNtCreateFile = 0x1;
const unsigned kCallNtOpenFile = 0x2;
const unsigned kCallNtQueryAttributesFile = 0x4;
const unsigned kCallNtQueryFullAttributesFile = 0x8;
const unsigned kCallNtSetInfoRename = 0x10;

DWORD rule_to_add = kCallNtOpenFile | kCallNtCreateFile |
kCallNtQueryAttributesFile |
kCallNtQueryFullAttributesFile | kCallNtSetInfoRename;
//创建 5 个PolicyRule,分别管制5种请求,action为ASK_BROKER
PolicyRule create(result);
PolicyRule open(result);
PolicyRule query(result);
PolicyRule query_full(result);
PolicyRule rename(result);
//根据不同的semantics,添加不同的 policy
switch (semantics) {
case TargetPolicy::FILES_ALLOW_DIR_ANY: {
open.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND);
create.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND);
break;
}
case TargetPolicy::FILES_ALLOW_READONLY: {
// We consider all flags that are not known to be readonly as potentially
// used for write.
DWORD allowed_flags = FILE_READ_DATA | FILE_READ_ATTRIBUTES |
FILE_READ_EA | SYNCHRONIZE | FILE_EXECUTE |
GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL;
DWORD restricted_flags = ~allowed_flags;
open.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND);
open.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL);
create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND);
create.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL);

// Read only access don't work for rename.
rule_to_add &= ~kCallNtSetInfoRename;
break;
}
case TargetPolicy::FILES_ALLOW_QUERY: {
// Here we don't want to add policy for the open or the create.
rule_to_add &=
~(kCallNtOpenFile | kCallNtCreateFile | kCallNtSetInfoRename);
break;
}
case TargetPolicy::FILES_ALLOW_ANY: {
break;
}
default: {
NOTREACHED();
return false;
}
}
// 根据当前rule_to_add的状态,把OpenFile::NAME这一参数进行设置
// PolicyRule add好以后,就通过policy->AddRule添加到LowLevelPolicy中
// 注意AddRule时会绑定service id和PolicyRule
// filesystem子系统占用了5个service id,分别对应open, create, rename, query, queryFull
if ((rule_to_add & kCallNtCreateFile) &&
(!create.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) ||
!policy->AddRule(IpcTag::NTCREATEFILE, &create))) {
return false;
}

if ((rule_to_add & kCallNtOpenFile) &&
(!open.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) ||
!policy->AddRule(IpcTag::NTOPENFILE, &open))) {
return false;
}

if ((rule_to_add & kCallNtQueryAttributesFile) &&
(!query.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) ||
!policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &query))) {
return false;
}

if ((rule_to_add & kCallNtQueryFullAttributesFile) &&
(!query_full.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) ||
!policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &query_full))) {
return false;
}

if ((rule_to_add & kCallNtSetInfoRename) &&
(!rename.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) ||
!policy->AddRule(IpcTag::NTSETINFO_RENAME, &rename))) {
return false;
}

return true;
}

以下是调用的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//chromium/sandbox/win/src/sandbox_policy_base.cc
ResultCode PolicyBase::AddRuleInternal(SubSystem subsystem,
Semantics semantics,
const wchar_t* pattern) {
if (!policy_) {
// 如果policy_未被设置,make一个4096*14尺寸的PolicyGlobal
policy_ = MakeBrokerPolicyMemory();
DCHECK(policy_);
// 使用该PolicyGlobal new出一个LowLevelPolicy
policy_maker_ = new LowLevelPolicy(policy_);
DCHECK(policy_maker_);
}

switch (subsystem) {
case SUBSYS_FILES: {
if (!file_system_init_) { //确定 filesystem 被初始化,一开始file_system_init_是false
if (!FileSystemPolicy::SetInitialRules(policy_maker_))
// 执行完SetInitialRules后,file_system_init_置true,即SetInitialRules只执行一次
return SBOX_ERROR_BAD_PARAMS;
file_system_init_ = true;
}
// 根据传入的pattern,semantics,用LowLevelPolicy生成一个rule
if (!FileSystemPolicy::GenerateRules(pattern, semantics, policy_maker_)) {
NOTREACHED();
return SBOX_ERROR_BAD_PARAMS;
}
break;
}
...
...
}

return SBOX_ALL_OK;
}

FileSystemPolicy::SetInitialRules()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
bool FileSystemPolicy::SetInitialRules(LowLevelPolicy* policy) {
PolicyRule format(ASK_BROKER);
PolicyRule short_name(ASK_BROKER);
// 两个ASK_BROKER的action rule

bool rv = format.AddNumberMatch(IF_NOT, FileName::BROKER, BROKER_TRUE, AND);
rv &= format.AddStringMatch(IF_NOT, FileName::NAME, L"\\/?/?\\*",
CASE_SENSITIVE);
// format按位与匹配FileName::BROKER没有BROKER_TRUE标志位
rv &= short_name.AddNumberMatch(IF_NOT, FileName::BROKER, BROKER_TRUE, AND);
// 匹配FileName::NAME不能为L"\\/?/?\\*"
rv &= short_name.AddStringMatch(IF, FileName::NAME, L"*~*", CASE_SENSITIVE);

// 为5个service id,各添加这两个rule到LowLevelPolicy
if (!rv || !policy->AddRule(IpcTag::NTCREATEFILE, &format))
return false;

if (!policy->AddRule(IpcTag::NTCREATEFILE, &short_name))
return false;

if (!policy->AddRule(IpcTag::NTOPENFILE, &format))
return false;

if (!policy->AddRule(IpcTag::NTOPENFILE, &short_name))
return false;

if (!policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &format))
return false;

if (!policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &short_name))
return false;

if (!policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &format))
return false;

if (!policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &short_name))
return false;

if (!policy->AddRule(IpcTag::NTSETINFO_RENAME, &format))
return false;

if (!policy->AddRule(IpcTag::NTSETINFO_RENAME, &short_name))
return false;

return true;
}

整个初始化函数为 low-level policy 定制一了一套初级的策略。

小结

FileSystem的low-level policy部分做了两件事:

  • PolicyBase::AddRule提供了该子系统5个service或者叫action的Rule制定接口。
  • 编写了broker端5个(create, open, rename, query, queryFull)请求的处理,在broker端调用了对应的API函数。

edge://sandbox

当我们在 edge 输入 edge://sandbox 会返回如下界面

image-20210728192811294

粗略的看,里面包含一个活动进程表(不包括浏览器进程,因为它没有被沙箱化)和一些摘要信息,然后和每个进程的详细沙箱配置的原始数据转储成的 JSON 序列。关于像 NtCreateFile这些service的 policy 是如何设置的在上文已经进行过解读,下文主要分析一下这个页面是什么含义。

以以下序列为例,进行解读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
"desiredIntegrityLevel": "S-1-16-4096 Low",
"desiredMitigations": "0000000000af1267",
"disconnectCsrss": "disabled",
"jobLevel": "Limited User",
"lockdownLevel": "Limited",
"platformMitigations": "01111001000110000000000000010000",
"policyRules": {
"CreateNamedPipeW": [
"prefix_i(p[0], '\\\\.\\pipe\\LOCAL\\chrome.sync.') -> askBroker"
],
"NtCreateFile": [
"!(p[1] & 1) && !(prefix(p[0], '\\??\\')) -> askBroker",
"!(p[1] & 1) && scan(p[0], '~') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\chrome.') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\LOCAL\\chrome.') -> askBroker"
],
"NtOpenFile": [
"!(p[1] & 1) && !(prefix(p[0], '\\??\\')) -> askBroker",
"!(p[1] & 1) && scan(p[0], '~') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\chrome.') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\LOCAL\\chrome.') -> askBroker"
],
"NtQueryAttributesFile": [
"!(p[1] & 1) && !(prefix(p[0], '\\??\\')) -> askBroker",
"!(p[1] & 1) && scan(p[0], '~') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\chrome.') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\LOCAL\\chrome.') -> askBroker"
],
"NtQueryFullAttributesFile": [
"!(p[1] & 1) && !(prefix(p[0], '\\??\\')) -> askBroker",
"!(p[1] & 1) && scan(p[0], '~') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\chrome.') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\LOCAL\\chrome.') -> askBroker"
],
"NtSetInfoRename": [
"!(p[1] & 1) && !(prefix(p[0], '\\??\\')) -> askBroker",
"!(p[1] & 1) && scan(p[0], '~') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\chrome.') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\LOCAL\\chrome.') -> askBroker"
]
},
"processIds": [
4888
]
}

关于Mitigations我们可以使用 google 提供的解码器进行解码:https://docs.google.com/a/chromium.org/viewer?a=v&pid=sites&srcid=Y2hyb21pdW0ub3JnfGRldnxneDo3MDg0MDMzODNjODgzMDMy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Paste mitigation values from chrome://sandbox output.
Chrome (desiredMitigations):
0000000000af1267

Platform (platformMitigations):
01111001000110000000000000010000

MITIGATION_DEP
MITIGATION_DEP_NO_ATL_THUNK
MITIGATION_SEHOP
MITIGATION_HEAP_TERMINATE
MITIGATION_BOTTOM_UP_ASLR
MITIGATION_DLL_SEARCH_ORDER
MITIGATION_EXTENSION_POINT_DISABLE
MITIGATION_NONSYSTEM_FONT_DISABLE
MITIGATION_FORCE_MS_SIGNED_BINS
MITIGATION_IMAGE_LOAD_NO_REMOTE
MITIGATION_IMAGE_LOAD_NO_LOW_LABEL
MITIGATION_RESTRICT_INDIRECT_BRANCH_PREDICTION

HEAP_TERMINATE_ALWAYS_ON
BOTTOM_UP_ASLR_ALWAYS_ON
EXTENSION_POINT_DISABLE_ALWAYS_ON
BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
FONT_DISABLE_ALWAYS_ON
IMAGE_LOAD_NO_REMOTE_ALWAYS_ON
IMAGE_LOAD_NO_LOW_LABEL_ALWAYS_ON
RESTRICT_INDIRECT_BRANCH_PREDICTION_ALWAYS_ON

其中很大一部分都是在构造函数中传递的数值,这里主要分析一下policyRules,调用如下函数:

GetPolicyRules

1
2
3
4
5
6
7
8
9
10
11
12
13
base::Value GetPolicyRules(const PolicyGlobal* policy_rules) {
DCHECK(policy_rules);
base::Value results(base::Value::Type::DICTIONARY);

for (size_t i = 0; i < kMaxServiceCount; i++) {
if (!policy_rules->entry[i])
continue;
IpcTag service = static_cast<IpcTag>(i);
results.SetKey(GetIpcTagAsString(service), //根据 service 去获取其policy_rules
GetPolicyOpcodes(policy_rules, service));
}
return results;
}

GetPolicyRules内部又会调用GetIpcTagAsString来获取 Ipc 的 tag,如:NtCreateFile这些,然后紧接着对这些 tag 的 policy 进行解析,调用GetPolicyOpcodes

entry指针数组,用以索引PolicyGlobal内每个PolicyBufferPolicyBuffer 是一个结构体,它包含要按顺序创建或评估的所有操作码。

struct PolicyBuffer {

size_t opcode_count;

PolicyOpcode opcodes[1];

};

GetPolicyOpcodes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
base::Value GetPolicyOpcodes(const PolicyGlobal* policy_rules, IpcTag service) {
base::Value entry(base::Value::Type::LIST);
PolicyBuffer* policy_buffer = policy_rules->entry[static_cast<size_t>(service)];
// Build up rules and emit when we hit an action.
std::string cur_rule;
for (size_t i = 0; i < policy_buffer->opcode_count; i++) {
const PolicyOpcode* opcode = &policy_buffer->opcodes[i];
if (opcode->GetID() != OP_ACTION) { //获取 opcodeID,也就是 OPCODE 类型
DCHECK(i + 1 < policy_buffer->opcode_count)
<< "Non-actions should not terminate rules";
//如果下个 OPCODE 类型不是OP_ACTION,则返回 true
bool peak = policy_buffer->opcodes[i + 1].GetID() != OP_ACTION;
//获取opcode
cur_rule += GetPolicyOpcode(opcode, peak);
} else {
cur_rule += " -> ";
cur_rule += GetPolicyOpcode(opcode, false);
entry.Append(cur_rule);
cur_rule.clear();
}
}
return entry;
}

GetPolicyOpcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
std::string GetPolicyOpcode(const PolicyOpcode* opcode, bool continuation) {
// See |policy_engine_opcodes.cc|.
uint32_t args[4];
auto options = opcode->GetOptions(); //获取Options,如kPolNegateEval
auto param = opcode->GetParameter(); //返回操作码所关系的函数的参数。
std::string condition;

if (options & kPolNegateEval) //检测 opcode 是否标志kPolNegateEval
condition += "!(";

switch (opcode->GetID()) {
case OP_ALWAYS_FALSE:
condition += "false";
break;
case OP_ALWAYS_TRUE:
condition += "true";
break;
case OP_NUMBER_MATCH:
opcode->GetArgument(1, &args[1]); //返回存储的参数
if (args[1] == UINT32_TYPE) { //UINT32_TYPE:支持的 C++ 类型编码为数字 id
opcode->GetArgument(0, &args[0]);
condition += base::StringPrintf("p[%d] == %x", param, args[0]);
} else {
const void* match_ptr = nullptr;
opcode->GetArgument(0, &match_ptr);
condition += base::StringPrintf("p[%d] == %p", param, match_ptr);
}
break;
case OP_NUMBER_MATCH_RANGE:
opcode->GetArgument(0, &args[0]);
opcode->GetArgument(1, &args[1]);
condition +=
base::StringPrintf("%x <= p[%d] <= %x", args[0], param, args[1]);
break;
case OP_NUMBER_AND_MATCH:
opcode->GetArgument(0, &args[0]);
condition += base::StringPrintf("p[%d] & %x", param, args[0]);
break;
case OP_WSTRING_MATCH: {
int pos;
opcode->GetArgument(1, &args[1]); // Length.
opcode->GetArgument(2, &pos); // Position.
opcode->GetArgument(3, &args[3]); // Options.
// These are not nul-terminated so we have to wrap them here.
//检索字符串参数的实际地址
auto match_string = std::wstring(opcode->GetRelativeString(0), 0,
static_cast<size_t>(args[1]));
condition += GetStringMatchOperation(pos, args[3]);
if (args[3] & CASE_INSENSITIVE) // if Options = 1
condition += "_i";
condition +=
base::StringPrintf("(p[%d], '%S')", param, match_string.c_str());
} break;
case OP_ACTION:
opcode->GetArgument(0, &args[0]);
condition += GetOpcodeAction(static_cast<EvalResult>(args[0]));
break;
default:
DCHECK(false) << "Unknown Opcode";
return "Unknown";
}

if (options & kPolNegateEval)
condition += ")";
// If there is another rule add a joining token.
if (continuation) {
if (options & kPolUseOREval)
condition += " || ";
else
condition += " && ";
}
return condition;
}

GetStringMatchOperation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::string GetStringMatchOperation(int pos, uint32_t options) {
if (pos == 0) {
if (options & EXACT_LENGTH)
return "exact";
else
return "prefix";
} else if (pos < 0) {
return "scan";
} else if (pos == kSeekToEnd) {
return "ends";
} else {
DCHECK(false) << "Invalid pos (" << pos << ")";
return "unknown";
}
}

StringMatchOptions

  • CASE_SENSITIVE = 0:Pay or Not attention to the case as defined by
  • CASE_INSENSITIVE = 1:RtlCompareUnicodeString windows API.
  • EXACT_LENGTH = 2:Don’t do substring match. Do full string match.

小结

1
2
3
4
5
6
"NtCreateFile": [
"!(p[1] & 1) && !(prefix(p[0], '\\??\\')) -> askBroker",
"!(p[1] & 1) && scan(p[0], '~') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\chrome.') -> askBroker",
"prefix_i(p[0], '\\??\\pipe\\LOCAL\\chrome.') -> askBroker"
],

上述其实描述的是一个 opcode 组内的 opcode 的值是什么。

!(:opcode 的 option 为 kPolNegateEval

p[1] & 1: & 代表opcode 的类型为OP_NUMBER_AND_MATCH,原函数args[1]=1

&&:options 不为kPolUseOREval

prefix:String Match Options 不为EXACT_LENGTH

总结:这些数据整体上描述了一个 opcode 组的内容,这个 opcode 组构成了该函数的高级缓解策略。