7
7
import os
8
8
import stat
9
9
from collections .abc import Iterable
10
- from .helpers import construct_selinux_context , selinux_check_access
10
+ from .helpers import (
11
+ construct_selinux_context ,
12
+ selinux_check_access ,
13
+ selinux_label_lookup ,
14
+ )
11
15
12
16
if os .name == 'posix' :
13
17
import pwd
@@ -143,6 +147,7 @@ class DatabaseWriter(DatabaseCommon):
143
147
def __init__ (self , path : str ):
144
148
self .con = sqlite3 .connect (path , isolation_level = None )
145
149
self .cur = self .con .cursor ()
150
+ self ._prepare_accesses ()
146
151
147
152
def insert_access (
148
153
self , case_id : int , subject_cid : int , path_rowid : int
@@ -169,24 +174,14 @@ def insert_result(
169
174
'''INSERT INTO results VALUES(?, ?, null, ?)
170
175
ON CONFLICT (access_id, operation_id)
171
176
DO UPDATE SET medusa_result = ?''' ,
172
- (
173
- access_id ,
174
- perm_id ,
175
- result_med ,
176
- result_med
177
- ),
177
+ (access_id , perm_id , result_med , result_med ),
178
178
)
179
179
elif result_med is None :
180
180
self .cur .execute (
181
181
'''INSERT INTO results VALUES(?, ?, ?, null)
182
182
ON CONFLICT (access_id, operation_id)
183
183
DO UPDATE SET reference_result = ?''' ,
184
- (
185
- access_id ,
186
- perm_id ,
187
- result_ref ,
188
- result_ref
189
- ),
184
+ (access_id , perm_id , result_ref , result_ref ),
190
185
)
191
186
else :
192
187
raise Exception ('One of the results has to be None.' )
@@ -198,6 +193,35 @@ def insert_or_select_access(
198
193
return self .insert_access (case_id , subject_cid , path_rowid )
199
194
return rowid
200
195
196
+ def _insert_selinux_access (
197
+ self ,
198
+ ):
199
+ is_dir = stat .S_ISDIR (_type )
200
+ _class = 'dir' if is_dir else 'file'
201
+
202
+ results = [
203
+ selinux_check_access (subject_context , context , _class , perm )
204
+ for perm in perms
205
+ ]
206
+ if verbose :
207
+ for perm , result in zip (perms , results ):
208
+ print (
209
+ f'{ subject_context } =>{ context } { path } ({ _class } :{ perm } )={ result } '
210
+ )
211
+ self .cur .execute (
212
+ 'INSERT INTO accesses VALUES(?, ?, ?)' ,
213
+ (
214
+ case_id ,
215
+ subject_cid ,
216
+ rowid ,
217
+ ),
218
+ )
219
+
220
+ access_id = self .cur .lastrowid
221
+
222
+ for perm_id , result in zip (perms_id , results ):
223
+ self .insert_result (access_id , perm_id , result , None )
224
+
201
225
def insert_selinux_accesses (
202
226
self ,
203
227
case_name : str ,
@@ -218,7 +242,6 @@ def insert_selinux_accesses(
218
242
subject.
219
243
:param verbose: Turns on verbose output.
220
244
"""
221
- self ._prepare_accesses ()
222
245
self .cur .execute (
223
246
'INSERT INTO cases VALUES(?) ON CONFLICT DO NOTHING' ,
224
247
(case_name ,),
@@ -275,6 +298,138 @@ def insert_selinux_accesses(
275
298
for perm_id , result in zip (perms_id , results ):
276
299
self .insert_result (access_id , perm_id , result , None )
277
300
301
+ def fill_missing_selinux_accesses (
302
+ self ,
303
+ case_name : str ,
304
+ subject_context : str ,
305
+ object_types : Iterable [str ],
306
+ verbose : bool = False ,
307
+ ):
308
+ """Fill missing accesses for SELinux in the database.
309
+
310
+ Fill a table with SELinux accesses from `subject_context` to all files
311
+ with types from `selinux_types`.
312
+
313
+ :param case_name: Name of the service that is examined. This will be
314
+ used as a unique value in the database.
315
+ :param subject_context: SELinux context of the subject.
316
+ :param object_types:SELinux types that will be searched in the database
317
+ and found files will be examined for read and write permissions from the
318
+ subject.
319
+ :param verbose: Turns on verbose output.
320
+ """
321
+ perms = ('read' , 'write' )
322
+ perms_id = self .get_operations_id (perms )
323
+
324
+ res = self .cur .execute (
325
+ """WITH RECURSIVE child AS
326
+ (SELECT accesses.rowid AS access_rowid,
327
+ accesses.node_rowid,
328
+ accesses.subject_cid,
329
+ contexts.name AS subject_context,
330
+ fs.rowid,
331
+ fs.parent,
332
+ fs.name,
333
+ type,
334
+ fs.selinux_user,
335
+ fs.selinux_role,
336
+ fs.selinux_type,
337
+ fs.selinux_category,
338
+ fs.selinux_sensitivity,
339
+ operations.rowid AS operation_id,
340
+ operation,
341
+ results.reference_result,
342
+ results.medusa_result
343
+ FROM accesses
344
+ JOIN contexts ON subject_cid = contexts.rowid
345
+ JOIN fs ON node_rowid = fs.rowid
346
+ LEFT JOIN results ON accesses.ROWID = results.access_id
347
+ LEFT JOIN operations ON results.operation_id = operations.rowid
348
+ WHERE case_id = 1
349
+ AND reference_result IS NULL
350
+ UNION ALL SELECT access_rowid,
351
+ node_rowid,
352
+ subject_cid,
353
+ subject_context,
354
+ fs.rowid,
355
+ fs.parent,
356
+ fs.name || '/' || child.name,
357
+ child.type,
358
+ child.selinux_user,
359
+ child.selinux_role,
360
+ child.selinux_type,
361
+ child.selinux_category,
362
+ child.selinux_sensitivity,
363
+ operation_id,
364
+ operation,
365
+ reference_result,
366
+ medusa_result
367
+ FROM fs,
368
+ child
369
+ WHERE child.parent = fs.rowid )
370
+ SELECT access_rowid,
371
+ subject_cid,
372
+ subject_context,
373
+ node_rowid,
374
+ name AS path,
375
+ TYPE,
376
+ selinux_user || ':' || selinux_role || ':' || selinux_type || ':' || selinux_sensitivity || COALESCE(':' || selinux_category, '') AS selinux_context,
377
+ operation_id,
378
+ operation,
379
+ reference_result,
380
+ medusa_result
381
+ FROM child
382
+ WHERE rowid = 1
383
+ """
384
+ )
385
+ accesses = res .fetchall ()
386
+ for (
387
+ access_id ,
388
+ subject_cid ,
389
+ subject_context ,
390
+ node_rowid ,
391
+ path ,
392
+ _type ,
393
+ selinux_context ,
394
+ operation_id ,
395
+ operation ,
396
+ reference_result ,
397
+ medusa_result ,
398
+ ) in accesses :
399
+ if selinux_context is None :
400
+ selinux_context = selinux_label_lookup (path , _type )
401
+
402
+ is_dir = stat .S_ISDIR (_type )
403
+ _class = 'dir' if is_dir else 'file'
404
+
405
+ if operation_id is None :
406
+ # Computer access permissions for all operations
407
+ results = [
408
+ selinux_check_access (
409
+ subject_context , selinux_context , _class , perm
410
+ )
411
+ for perm in perms
412
+ ]
413
+ if verbose :
414
+ for perm , result in zip (perms , results ):
415
+ print (
416
+ f'{ subject_context } =>{ selinux_context } { path } ({ _class } :{ perm } )={ result } '
417
+ )
418
+
419
+ for perm_id , result in zip (perms_id , results ):
420
+ self .insert_result (access_id , perm_id , result , None )
421
+ continue
422
+ # Updating just one operation
423
+ result = selinux_check_access (
424
+ subject_context , selinux_context , _class , operation
425
+ )
426
+ if verbose :
427
+ print (
428
+ f'{ subject_context } =>{ selinux_context } { path } ({ _class } :{ operation } )={ result } '
429
+ )
430
+
431
+ self .insert_result (access_id , operation_id , result , None )
432
+
278
433
def _prepare_accesses (self ):
279
434
self .cur .execute (
280
435
"""CREATE TABLE IF NOT EXISTS accesses (
@@ -341,9 +496,9 @@ def _prepare_accesses(self):
341
496
fs.rowid,
342
497
fs.parent,
343
498
fs.name || '/' || child.name,
344
- fs .selinux_user,
345
- fs .selinux_role,
346
- fs .selinux_type,
499
+ child .selinux_user,
500
+ child .selinux_role,
501
+ child .selinux_type,
347
502
operation,
348
503
reference_result,
349
504
medusa_result
0 commit comments