summaryrefslogtreecommitdiff
path: root/policychecker/rules.xsl
blob: bc306d9badc28d0768ced01fd5194de98d069c24 (plain)
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
<?xml version="1.0" standalone="yes"?>

<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">

	<sch:pattern name="No '*' anywhere">
		<!-- don't place any more rules here as probably they won't fire -->
		<sch:rule context="*[@*]">
			<!-- here we use report, not assert, as report with @*='*' works as "if any attribute matches", while assert @*!='*' works as "if all attributes match" -->
			<sch:report test="@* = '*'">Rules using "*" are not allowed.</sch:report>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="Default denials not specified (send_destination, send_destination_prefix, own, own_prefix)">
		<sch:rule context="allow[@send_destination]">
			<sch:let name="dest_name" value="@send_destination"/>
			<!-- We need to check if there is 'deny own_prefix' for any prefix of send_destination.
			     Thus, we generate 8 (this is an arbitrary number) prefixes and check against them.

				 For example, if send_destination="a.b.c.d.e.f.g.h.i", then the generated prefixes are:
				 a, a.b, a.b.c, a.b.c.d, a.b.c.d.e, a.b.c.d.e.f, a.b.c.d.e.f.g, a.b.c.d.e.f.g.h
				 A warning will show up if there is no 'deny own' for a.b.c.d.e.f.g.h.i and
				 no 'deny own_prefix' for a.b.c.d.e.f.g.h.i and all the above prefixes.

				 In case of some shorter destinations, e.g. a.b.c, the generated prefixes are:
				 a, a.b, a.b., a.b.., a.b..., a.b...., a.b....., a.b......
				 but only two of them are valid values anyway.
				 A warning will show up if there is no 'deny own' for a.b.c and
				 no 'deny own_prefix' for a.b.c and all the above prefixes.

				 It probably covers most cases. If not, it can be extended.
			-->
			<sch:let name="prefix1" value="substring-before($dest_name, '.')"/>
			<sch:let name="prefix2" value="concat(concat($prefix1, '.'), substring-before(substring-after($dest_name, concat($prefix1, '.')), '.'))"/>
			<sch:let name="prefix3" value="concat(concat($prefix2, '.'), substring-before(substring-after($dest_name, concat($prefix2, '.')), '.'))"/>
			<sch:let name="prefix4" value="concat(concat($prefix3, '.'), substring-before(substring-after($dest_name, concat($prefix3, '.')), '.'))"/>
			<sch:let name="prefix5" value="concat(concat($prefix4, '.'), substring-before(substring-after($dest_name, concat($prefix4, '.')), '.'))"/>
			<sch:let name="prefix6" value="concat(concat($prefix5, '.'), substring-before(substring-after($dest_name, concat($prefix5, '.')), '.'))"/>
			<sch:let name="prefix7" value="concat(concat($prefix6, '.'), substring-before(substring-after($dest_name, concat($prefix6, '.')), '.'))"/>
			<sch:let name="prefix8" value="concat(concat($prefix7, '.'), substring-before(substring-after($dest_name, concat($prefix7, '.')), '.'))"/>
			<sch:assert test="//policy[@context='default']/deny[@send_destination = $dest_name]">For each allow send_destination you must add a deny send_destination in default context.</sch:assert>
			<sch:assert test="//policy[@context='default']/deny[@own = $dest_name] or
			                  //policy[@context='default']/deny[@own_prefix = $dest_name] or
			                  //policy[@context='default']/deny[@own_prefix = $prefix1] or
			                  //policy[@context='default']/deny[@own_prefix = $prefix2] or
			                  //policy[@context='default']/deny[@own_prefix = $prefix3] or
			                  //policy[@context='default']/deny[@own_prefix = $prefix4] or
			                  //policy[@context='default']/deny[@own_prefix = $prefix5] or
			                  //policy[@context='default']/deny[@own_prefix = $prefix6] or
			                  //policy[@context='default']/deny[@own_prefix = $prefix7] or
			                  //policy[@context='default']/deny[@own_prefix = $prefix8]
				">For each allow send_destination you must add a deny own or deny own_prefix in default context.
			</sch:assert>
		</sch:rule>
		<sch:rule context="allow[@send_destination_prefix]">
			<sch:let name="dest_name" value="@send_destination_prefix"/>
			<sch:assert test="//policy[@context='default']/deny[@send_destination_prefix = $dest_name]">For each allow send_destination_prefix you must add a deny send_destination_prefix in default context.</sch:assert>
			<sch:assert test="//policy[@context='default']/deny[@own_prefix = $dest_name]">For each allow send_destination_prefix you must add a deny own_prefix in default context</sch:assert>
		</sch:rule>
		<sch:rule context="allow[@own]">
			<sch:let name="dest_name" value="@own"/>
			<sch:assert test="//policy[@context='default']/deny[@own = $dest_name]">For each allow own you must add a deny own in default context.</sch:assert>
		</sch:rule>
		<sch:rule context="allow[@own_prefix]">
			<sch:let name="dest_name" value="@own_prefix"/>
			<sch:assert test="//policy[@context='default']/deny[@own_prefix = $dest_name]">For each allow own_prefix you must add a deny own_prefix in default context.</sch:assert>
		</sch:rule>
		<sch:rule context="deny[@own]">
			<sch:let name="dest_name" value="@own"/>
			<sch:assert test="//policy/allow[@own = $dest_name] or //policy/check[@own = $dest_name]">"deny own" present, but no "allow own" or "check own" for that name.</sch:assert>
		</sch:rule>
		<sch:rule context="deny[@own_prefix]">
			<sch:let name="dest_name" value="@own_prefix"/>
			<sch:assert test="//policy/allow[@own_prefix = $dest_name] or //policy/check[@own_prefix = $dest_name]">"deny own_prefix" present, but no "allow own_prefix" or "check own_prefix" for that name.</sch:assert>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="Unconstrained allow in default context (or mandatory)">
		<!-- policy[@context] means that it applies to both default and mandatory contexts -->
		<sch:rule context="policy[@context]/allow[@send_type='method_call']">
			<sch:assert test="@send_destination or @send_destination_prefix">Unconstrained allows are not allowed in context default and context mandatory: missing send_destination or send_destination_prefix, but send_type="method_call" is present.</sch:assert>
		</sch:rule>
		<sch:rule context="policy[@context]/allow[@send_path]">
			<sch:assert test="@send_destination or @send_destination_prefix or (@send_interface and @send_type='signal')">Unconstrained allows are not allowed in context default and context mandatory: missing send_destination or send_destination_prefix for non-signal, but send_path is present.</sch:assert>
		</sch:rule>
		<sch:rule context="policy[@context]/allow[@receive_type='method_call']">
			<sch:assert test="@receive_sender">Unconstrained allows are not allowed in context default and context mandatory: missing receive_sender, but receive_type="method_call" is present.</sch:assert>
		</sch:rule>
		<sch:rule context="policy[@context]/allow[@receive_path]">
			<sch:assert test="@receive_sender">Unconstrained allows are not allowed in context default and context mandatory: missing receive_sender, but receive_path is present.</sch:assert>
		</sch:rule>
		<!-- user and group contexts need at least send_destination -->
		<sch:rule context="policy[@user]/allow[@send_type='method_call'] | policy[@group]/allow[@send_type='method_call']">
			<sch:assert test="@send_destination or @send_destination_prefix">Unconstrained allows are not allowed in user or group context: missing send_destination or send_destination_prefix, but send_type="method_call" is present.</sch:assert>
		</sch:rule>
		<sch:rule context="policy[@user]/allow[@send_path] | policy[@group]/allow[@send_path]">
			<sch:assert test="@send_destination or @send_destination_prefix or (@send_interface and @send_type='signal')">Unconstrained allows are not allowed in user or group context: missing send_destination or send_destination_prefix for non-signal, but send_path is present.</sch:assert>
		</sch:rule>
		<sch:rule context="policy[@user]/allow[@receive_type='method_call'] | policy[@group]/allow[@receive_type='method_call']">
			<sch:assert test="@receive_sender">Unconstrained allows are not allowed in user or group context: missing receive_sender, but receive_type="method_call" is present.</sch:assert>
		</sch:rule>
		<sch:rule context="policy[@user]/allow[@receive_path] | policy[@group]/allow[@receive_path]">
			<sch:assert test="@receive_sender">Unconstrained allows are not allowed in user or group context: missing receive_sender, but receive_path is present.</sch:assert>
		</sch:rule>
	</sch:pattern>
	<!-- additional rule -->
	<sch:pattern name="Don't depend on global deny-own and deny-method-call">
		<sch:rule context="busconfig">
			<sch:assert test="policy[@context='default']">You must provide a policy context-default section.</sch:assert>
		</sch:rule>
	</sch:pattern>
	<!-- -->


	<!-- TODO ineffective (masked) rules, TBD -->

	<!-- Duplicate rules in different contexts are processed using same.xsl -->

	<sch:pattern name="No empty policies">
		<sch:rule context="policy">
			<sch:report test="not(*)">Empty policy is not allowed.</sch:report>
		</sch:rule>
	</sch:pattern>

	<!-- we have to check Cynara privileges outside Schematron as xslt fails when given a rule with 1500 Cynara privileges (as taken from mobile emulator) -->
	<!--sch:pattern name="Invalid Cynara privilege">
		<sch:rule context="check">
			<sch:assert test="PRIVILEGES_TEST">Privilege does not exist.</sch:assert>
		</sch:rule>
	</sch:pattern-->

	<sch:pattern name="No at_console rules">
		<sch:rule context="policy[@at_console]/*">
			<!-- this will fail on many upstream packages which still have at_console rules despite at_console being deprecated since a long time -->
			<!-- use true() so that we print an error for every allow/deny, and not only for every policy at_console once -->
			<sch:report test="true()">You mustn't define rules in at_console contexts (it's deprecated on dbus-daemon systems and not supported on kdbus systems).</sch:report>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="Invalid user">
		<sch:rule context="*[@user]">
			<sch:assert test="@user = '*' or USERS_TEST">User does not exist.</sch:assert>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="Invalid group">
		<sch:rule context="*[@group]">
			<sch:assert test="@group = '*' or GROUPS_TEST">Group does not exist.</sch:assert>
			<sch:assert test="not(starts-with(@group, 'priv_'))">Group 'priv_*' is not allowed.</sch:assert>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="No SMACK-context policies">
		<sch:rule context="policy[@context]">
			<!-- I have no better idea how to check for SMACK labels. Policies must obey the test below anyway. -->
			<sch:report test="@context != 'default' and @context != 'mandatory'">You mustn't use SMACK-context policies, use privileges exclusively.</sch:report>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="No user/group rules outside policy context=default|mandatory">
		<sch:rule context="policy[@user|@group]/deny[@user|@group] | policy[@user|@group]/allow[@user|@group]">
			<sch:report test="true()">You mustn't allow/deny user/group anywhere except policy context=default|mandatory.</sch:report>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="No eavesdrop rules">
		<sch:rule context="allow|deny">
			<sch:report test="@eavesdrop">You mustn't use eavesdrop rules as they are a potential security risk.</sch:report>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="No complex globs">
		<sch:rule context="*[@*]">
			<!-- No ends-with() (xpath 2.0 in general) using xsltproc :( -->
			<sch:report test="string-length(@*) > 1 and substring(@*, string-length(@*)) = '*'">Globs like sth* are not allowed.</sch:report>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="No send_interface without send_destination">
		<sch:rule context="*[@send_interface]">
			<sch:assert test="@send_destination or @send_destination_prefix or @send_type='signal'">You mustn't use send_interface without send_destination or send_destination_prefix, unless you limit the rule to only signals with send_type="signal"</sch:assert>
		</sch:rule>
		<sch:rule context="*[@receive_interface]">
			<sch:assert test="@receive_sender or @receive_type='signal'">You mustn't use receive_interface without receive_sender</sch:assert>
		</sch:rule>
	</sch:pattern>

	<sch:pattern name="No send and receive in one rule">
		<sch:rule context="allow|deny">
			<sch:report test="(@send_interface or @send_member or @send_error or @send_broadcast or @send_destination or @send_destination_prefix or @send_type or @send_path or @send_requested_reply) and (@receive_interface or @receive_member or @receive_error or @receive_sender or @receive_type or @receive_path or @receive_requsted_reply)">You mustn't use send_ and receive_ attributes in one rule.</sch:report>
		</sch:rule>
	</sch:pattern>




	<!-- additional rule -->
	<sch:pattern name="Not implemented in libdbuspolicy">
		<sch:rule context="allow|deny">
			<sch:report test="@eavesdrop">eavesdrop rules not implemented on kdbus systems.</sch:report>
			<sch:report test="@send_error">send_error rules not implemented on kdbus systems.</sch:report>
			<sch:report test="@receive_error">send_error rules not implemented on kdbus systems.</sch:report>
			<sch:report test="@send_requested_reply">send_requested_reply rules not implemented on kdbus systems.</sch:report>
			<sch:report test="@receive_requested_reply">receive_requested_reply rules not implemented on kdbus systems.</sch:report>
			<sch:report test="@send_broadcast">send_broadcast rules not implemented on kdbus systems.</sch:report>
		</sch:rule>
	</sch:pattern>

</sch:schema>