xref: /aosp_15_r20/external/selinux/python/sepolgen/src/sepolgen/refparser.py (revision 2d543d20722ada2425b5bdab9d0d1d29470e7bba)
1# Authors: Karl MacMillan <[email protected]>
2#
3# Copyright (C) 2006-2007 Red Hat
4# see file 'COPYING' for use and warranty information
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; version 2 only
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19
20# OVERVIEW
21#
22#
23# This is a parser for the refpolicy policy "language" - i.e., the
24# normal SELinux policy language plus the refpolicy style M4 macro
25# constructs on top of that base language. This parser is primarily
26# aimed at parsing the policy headers in order to create an abstract
27# policy representation suitable for generating policy.
28#
29# Both the lexer and parser are included in this file. The are implemented
30# using the Ply library (included with sepolgen).
31
32import sys
33import os
34import re
35import traceback
36
37from . import access
38from . import defaults
39from . import lex
40from . import refpolicy
41from . import yacc
42
43# :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
44#
45# lexer
46#
47# :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
48
49tokens = (
50    # basic tokens, punctuation
51    'TICK',
52    'SQUOTE',
53    'OBRACE',
54    'CBRACE',
55    'SEMI',
56    'COLON',
57    'OPAREN',
58    'CPAREN',
59    'COMMA',
60    'MINUS',
61    'TILDE',
62    'ASTERISK',
63    'AMP',
64    'BAR',
65    'EXPL',
66    'EQUAL',
67    'FILENAME',
68    'IDENTIFIER',
69    'NUMBER',
70    'PATH',
71    'IPV6_ADDR',
72    # reserved words
73    #   module
74    'MODULE',
75    'POLICY_MODULE',
76    'REQUIRE',
77    #   flask
78    'SID',
79    'GENFSCON',
80    'FS_USE_XATTR',
81    'FS_USE_TRANS',
82    'FS_USE_TASK',
83    'PORTCON',
84    'NODECON',
85    'NETIFCON',
86    'PIRQCON',
87    'IOMEMCON',
88    'IOPORTCON',
89    'PCIDEVICECON',
90    'DEVICETREECON',
91    #   object classes
92    'CLASS',
93    #   types and attributes
94    'TYPEATTRIBUTE',
95    'ROLEATTRIBUTE',
96    'TYPE',
97    'ATTRIBUTE',
98    'ATTRIBUTE_ROLE',
99    'ALIAS',
100    'TYPEALIAS',
101    #   conditional policy
102    'BOOL',
103    'TRUE',
104    'FALSE',
105    'IF',
106    'ELSE',
107    #   users and roles
108    'ROLE',
109    'TYPES',
110    #   rules
111    'ALLOW',
112    'DONTAUDIT',
113    'AUDITALLOW',
114    'NEVERALLOW',
115    'PERMISSIVE',
116    'TYPEBOUNDS',
117    'TYPE_TRANSITION',
118    'TYPE_CHANGE',
119    'TYPE_MEMBER',
120    'RANGE_TRANSITION',
121    'ROLE_TRANSITION',
122    #   refpolicy keywords
123    'OPT_POLICY',
124    'INTERFACE',
125    'TUNABLE_POLICY',
126    'GEN_REQ',
127    'TEMPLATE',
128    'GEN_CONTEXT',
129    'GEN_TUNABLE',
130    #   m4
131    'IFELSE',
132    'IFDEF',
133    'IFNDEF',
134    'DEFINE'
135    )
136
137# All reserved keywords - see t_IDENTIFIER for how these are matched in
138# the lexer.
139reserved = {
140    # module
141    'module' : 'MODULE',
142    'policy_module' : 'POLICY_MODULE',
143    'require' : 'REQUIRE',
144    # flask
145    'sid' : 'SID',
146    'genfscon' : 'GENFSCON',
147    'fs_use_xattr' : 'FS_USE_XATTR',
148    'fs_use_trans' : 'FS_USE_TRANS',
149    'fs_use_task' : 'FS_USE_TASK',
150    'portcon' : 'PORTCON',
151    'nodecon' : 'NODECON',
152    'netifcon' : 'NETIFCON',
153    'pirqcon' : 'PIRQCON',
154    'iomemcon' : 'IOMEMCON',
155    'ioportcon' : 'IOPORTCON',
156    'pcidevicecon' : 'PCIDEVICECON',
157    'devicetreecon' : 'DEVICETREECON',
158    # object classes
159    'class' : 'CLASS',
160    # types and attributes
161    'typeattribute' : 'TYPEATTRIBUTE',
162    'roleattribute' : 'ROLEATTRIBUTE',
163    'type' : 'TYPE',
164    'attribute' : 'ATTRIBUTE',
165    'attribute_role' : 'ATTRIBUTE_ROLE',
166    'alias' : 'ALIAS',
167    'typealias' : 'TYPEALIAS',
168    # conditional policy
169    'bool' : 'BOOL',
170    'true' : 'TRUE',
171    'false' : 'FALSE',
172    'if' : 'IF',
173    'else' : 'ELSE',
174    # users and roles
175    'role' : 'ROLE',
176    'types' : 'TYPES',
177    # rules
178    'allow' : 'ALLOW',
179    'dontaudit' : 'DONTAUDIT',
180    'auditallow' : 'AUDITALLOW',
181    'neverallow' : 'NEVERALLOW',
182    'permissive' : 'PERMISSIVE',
183    'typebounds' : 'TYPEBOUNDS',
184    'type_transition' : 'TYPE_TRANSITION',
185    'type_change' : 'TYPE_CHANGE',
186    'type_member' : 'TYPE_MEMBER',
187    'range_transition' : 'RANGE_TRANSITION',
188    'role_transition' : 'ROLE_TRANSITION',
189    # refpolicy keywords
190    'optional_policy' : 'OPT_POLICY',
191    'interface' : 'INTERFACE',
192    'tunable_policy' : 'TUNABLE_POLICY',
193    'gen_require' : 'GEN_REQ',
194    'template' : 'TEMPLATE',
195    'gen_context' : 'GEN_CONTEXT',
196    'gen_tunable' : 'GEN_TUNABLE',
197    # M4
198    'ifelse' : 'IFELSE',
199    'ifndef' : 'IFNDEF',
200    'ifdef' : 'IFDEF',
201    'define' : 'DEFINE'
202    }
203
204# The ply lexer allows definition of tokens in 2 ways: regular expressions
205# or functions.
206
207# Simple regex tokens
208t_TICK      = r'\`'
209t_SQUOTE    = r'\''
210t_OBRACE    = r'\{'
211t_CBRACE    = r'\}'
212# This will handle spurious extra ';' via the +
213t_SEMI      = r'\;+'
214t_COLON     = r'\:'
215t_OPAREN    = r'\('
216t_CPAREN    = r'\)'
217t_COMMA     = r'\,'
218t_MINUS     = r'\-'
219t_TILDE     = r'\~'
220t_ASTERISK  = r'\*'
221t_AMP       = r'\&'
222t_BAR       = r'\|'
223t_EXPL      = r'\!'
224t_EQUAL     = r'\='
225t_NUMBER    = r'[0-9\.]+'
226t_PATH      = r'/[a-zA-Z0-9)_\.\*/\$]*'
227#t_IPV6_ADDR = r'[a-fA-F0-9]{0,4}:[a-fA-F0-9]{0,4}:([a-fA-F0-9]{0,4}:)*'
228
229# Ignore whitespace - this is a special token for ply that more efficiently
230# ignores uninteresting tokens.
231t_ignore    = " \t"
232
233# More complex tokens
234def t_IPV6_ADDR(t):
235    r'[a-fA-F0-9]{0,4}:[a-fA-F0-9]{0,4}:([a-fA-F0-9]|:)*'
236    # This is a function simply to force it sooner into
237    # the regex list
238    return t
239
240def t_m4comment(t):
241    r'dnl.*\n'
242    # Ignore all comments
243    t.lexer.lineno += 1
244
245def t_refpolicywarn1(t):
246    r'define.*refpolicywarn\(.*\n'
247    # Ignore refpolicywarn statements - they sometimes
248    # contain text that we can't parse.
249    t.skip(1)
250
251def t_refpolicywarn(t):
252    r'refpolicywarn\(.*\n'
253    # Ignore refpolicywarn statements - they sometimes
254    # contain text that we can't parse.
255    t.lexer.lineno += 1
256
257def t_IDENTIFIER(t):
258    r'[a-zA-Z_\$][a-zA-Z0-9_\-\+\.\$\*~]*'
259    # Handle any keywords
260    t.type = reserved.get(t.value,'IDENTIFIER')
261    return t
262
263def t_FILENAME(t):
264    r'\"[a-zA-Z0-9_\-\+\.\$\*~ :\[\]]+\"'
265    # Handle any keywords
266    t.type = reserved.get(t.value,'FILENAME')
267    return t
268
269def t_comment(t):
270    r'\#.*\n'
271    # Ignore all comments
272    t.lexer.lineno += 1
273
274def t_error(t):
275    print("Illegal character '%s'" % t.value[0])
276    t.skip(1)
277
278def t_newline(t):
279    r'\n+'
280    t.lexer.lineno += len(t.value)
281
282# :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
283#
284# Parser
285#
286# :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
287
288# Global data used during parsing - making it global is easier than
289# passing the state through the parsing functions.
290
291#   m is the top-level data structure (stands for modules).
292m = None
293#   error is either None (indicating no error) or a string error message.
294error = None
295parse_file = ""
296#   spt is the support macros (e.g., obj/perm sets) - it is an instance of
297#     refpolicy.SupportMacros and should always be present during parsing
298#     though it may not contain any macros.
299spt = None
300success = True
301
302# utilities
303def collect(stmts, parent, val=None):
304    if stmts is None:
305        return
306    for s in stmts:
307        if s is None:
308            continue
309        s.parent = parent
310        if val is not None:
311            parent.children.insert(0, (val, s))
312        else:
313            parent.children.insert(0, s)
314
315def expand(ids, s):
316    for id in ids:
317        if spt.has_key(id):  # noqa
318            s.update(spt.by_name(id))
319        else:
320            s.add(id)
321
322# Top-level non-terminal
323def p_statements(p):
324    '''statements : statement
325                  | statements statement
326                  | empty
327    '''
328    if len(p) == 2 and p[1]:
329        m.children.append(p[1])
330    elif len(p) > 2 and p[2]:
331        m.children.append(p[2])
332
333def p_statement(p):
334    '''statement : interface
335                 | template
336                 | obj_perm_set
337                 | policy
338                 | policy_module_stmt
339                 | module_stmt
340    '''
341    p[0] = p[1]
342
343def p_empty(p):
344    'empty :'
345    pass
346
347#
348# Reference policy language constructs
349#
350
351# This is for the policy module statement (e.g., policy_module(foo,1.2.0)).
352# We have a separate terminal for either the basic language module statement
353# and interface calls to make it easier to identifier.
354def p_policy_module_stmt(p):
355    'policy_module_stmt : POLICY_MODULE OPAREN IDENTIFIER COMMA NUMBER CPAREN'
356    m = refpolicy.ModuleDeclaration()
357    m.name = p[3]
358    m.version = p[5]
359    m.refpolicy = True
360    p[0] = m
361
362def p_interface(p):
363    '''interface : INTERFACE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
364    '''
365    x = refpolicy.Interface(p[4])
366    collect(p[8], x)
367    p[0] = x
368
369def p_template(p):
370    '''template : TEMPLATE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
371                | DEFINE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
372    '''
373    x = refpolicy.Template(p[4])
374    collect(p[8], x)
375    p[0] = x
376
377def p_define(p):
378    '''define : DEFINE OPAREN TICK IDENTIFIER SQUOTE CPAREN'''
379    # This is for defining single M4 values (to be used later in ifdef statements).
380    # Example: define(`sulogin_no_pam'). We don't currently do anything with these
381    # but we should in the future when we correctly resolve ifdef statements.
382    p[0] = None
383
384def p_interface_stmts(p):
385    '''interface_stmts : policy
386                       | interface_stmts policy
387                       | empty
388    '''
389    if len(p) == 2 and p[1]:
390        p[0] = p[1]
391    elif len(p) > 2:
392        if not p[1]:
393            if p[2]:
394                p[0] = p[2]
395        elif not p[2]:
396            p[0] = p[1]
397        else:
398            p[0] = p[1] + p[2]
399
400def p_optional_policy(p):
401    '''optional_policy : OPT_POLICY OPAREN TICK interface_stmts SQUOTE CPAREN
402                       | OPT_POLICY OPAREN TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
403    '''
404    o = refpolicy.OptionalPolicy()
405    collect(p[4], o, val=True)
406    if len(p) > 7:
407        collect(p[8], o, val=False)
408    p[0] = [o]
409
410def p_tunable_policy(p):
411    '''tunable_policy : TUNABLE_POLICY OPAREN TICK cond_expr SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
412                      | TUNABLE_POLICY OPAREN TICK cond_expr SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN
413    '''
414    x = refpolicy.TunablePolicy()
415    x.cond_expr = p[4]
416    collect(p[8], x, val=True)
417    if len(p) > 11:
418        collect(p[12], x, val=False)
419    p[0] = [x]
420
421def p_ifelse_compare_value(p):
422    '''ifelse_compare_value : TICK IDENTIFIER SQUOTE
423                            | TICK TRUE       SQUOTE
424                            | TICK FALSE      SQUOTE
425                            | TICK            SQUOTE
426                            | empty
427    '''
428    if len(p) == 4:
429        p[0] = p[2]
430    else:
431        p[0] = None
432
433def p_ifelse_section(p):
434    '''ifelse_section : TICK IDENTIFIER SQUOTE COMMA ifelse_compare_value COMMA TICK interface_stmts SQUOTE
435    '''
436    x = refpolicy.IfElse(p[2])
437    collect(p[8], x, val=True)
438    p[0] = [x]
439
440def p_ifelse_sections(p):
441    '''ifelse_sections : ifelse_sections COMMA ifelse_section
442                       | ifelse_section
443    '''
444    if len(p) == 4:
445        p[0] = p[1] + p[3]
446    else:
447        p[0] = p[1]
448
449def p_ifelse(p):
450    '''ifelse : IFELSE OPAREN ifelse_sections COMMA TICK interface_stmts SQUOTE CPAREN optional_semi
451    '''
452    x = refpolicy.IfElse(p[3])
453    collect(p[3], x, val=True)
454    collect(p[6], x, val=False)
455    p[0] = [x]
456
457def p_ifdef(p):
458    '''ifdef : IFDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK statements SQUOTE CPAREN optional_semi
459             | IFNDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK statements SQUOTE CPAREN optional_semi
460             | IFDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK statements SQUOTE COMMA TICK statements SQUOTE CPAREN optional_semi
461    '''
462    x = refpolicy.IfDef(p[4])
463    if p[1] == 'ifdef':
464        v = True
465    else:
466        v = False
467    collect(p[8], x, val=v)
468    if len(p) > 12:
469        collect(p[12], x, val=False)
470    p[0] = [x]
471
472def p_interface_call(p):
473    '''interface_call : IDENTIFIER OPAREN interface_call_param_list CPAREN
474                      | IDENTIFIER OPAREN CPAREN
475                      | IDENTIFIER OPAREN interface_call_param_list CPAREN SEMI'''
476    # Allow spurious semi-colons at the end of interface calls
477    i = refpolicy.InterfaceCall(ifname=p[1])
478    if len(p) > 4:
479        i.args.extend(p[3])
480    p[0] = i
481
482def p_interface_call_param(p):
483    '''interface_call_param : IDENTIFIER
484                            | IDENTIFIER MINUS IDENTIFIER
485                            | MINUS IDENTIFIER
486                            | nested_id_set
487                            | TRUE
488                            | FALSE
489                            | FILENAME
490    '''
491    # Intentionally let single identifiers pass through
492    # List means set, non-list identifier
493    if len(p) == 2:
494        p[0] = p[1]
495    elif len(p) == 3:
496        p[0] = "-" + p[2]
497    else:
498        p[0] = [p[1], "-" + p[3]]
499
500def p_interface_call_param_list(p):
501    '''interface_call_param_list : interface_call_param
502                                 | interface_call_param_list COMMA interface_call_param
503    '''
504    if len(p) == 2:
505        p[0] = [p[1]]
506    else:
507        p[0] = p[1] + [p[3]]
508
509
510def p_obj_perm_set(p):
511    'obj_perm_set : DEFINE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK names SQUOTE CPAREN'
512    s = refpolicy.ObjPermSet(p[4])
513    s.perms = p[8]
514    p[0] = s
515
516#
517# Basic SELinux policy language
518#
519
520def p_policy(p):
521    '''policy : policy_stmt
522              | optional_policy
523              | tunable_policy
524              | ifdef
525              | ifelse
526              | conditional
527    '''
528    p[0] = p[1]
529
530def p_policy_stmt(p):
531    '''policy_stmt : gen_require
532                   | avrule_def
533                   | typerule_def
534                   | typebound_def
535                   | typeattribute_def
536                   | roleattribute_def
537                   | interface_call
538                   | role_def
539                   | role_allow
540                   | permissive
541                   | type_def
542                   | typealias_def
543                   | attribute_def
544                   | attribute_role_def
545                   | range_transition_def
546                   | role_transition_def
547                   | bool
548                   | gen_tunable
549                   | define
550                   | initial_sid
551                   | genfscon
552                   | fs_use
553                   | portcon
554                   | nodecon
555                   | netifcon
556                   | pirqcon
557                   | iomemcon
558                   | ioportcon
559                   | pcidevicecon
560                   | devicetreecon
561    '''
562    if p[1]:
563        p[0] = [p[1]]
564
565def p_module_stmt(p):
566    'module_stmt : MODULE IDENTIFIER NUMBER SEMI'
567    m = refpolicy.ModuleDeclaration()
568    m.name = p[2]
569    m.version = p[3]
570    m.refpolicy = False
571    p[0] = m
572
573def p_gen_require(p):
574    '''gen_require : GEN_REQ OPAREN TICK requires SQUOTE CPAREN
575                   | REQUIRE OBRACE requires CBRACE'''
576    # We ignore the require statements - they are redundant data from our point-of-view.
577    # Checkmodule will verify them later anyway so we just assume that they match what
578    # is in the rest of the interface.
579    pass
580
581def p_requires(p):
582    '''requires : require
583                | requires require
584                | ifdef
585                | requires ifdef
586                | ifelse
587                | requires ifelse
588    '''
589    pass
590
591def p_require(p):
592    '''require : TYPE comma_list SEMI
593               | ROLE comma_list SEMI
594               | ATTRIBUTE comma_list SEMI
595               | ATTRIBUTE_ROLE comma_list SEMI
596               | CLASS comma_list SEMI
597               | BOOL comma_list SEMI
598    '''
599    pass
600
601def p_security_context(p):
602    '''security_context : IDENTIFIER COLON IDENTIFIER COLON IDENTIFIER
603                        | IDENTIFIER COLON IDENTIFIER COLON IDENTIFIER COLON mls_range_def'''
604    # This will likely need some updates to handle complex levels
605    s = refpolicy.SecurityContext()
606    s.user = p[1]
607    s.role = p[3]
608    s.type = p[5]
609    if len(p) > 6:
610        s.level = p[7]
611
612    p[0] = s
613
614def p_gen_context(p):
615    '''gen_context : GEN_CONTEXT OPAREN security_context COMMA mls_range_def CPAREN
616    '''
617    # We actually store gen_context statements in a SecurityContext
618    # object - it knows how to output either a bare context or a
619    # gen_context statement.
620    s = p[3]
621    s.level = p[5]
622
623    p[0] = s
624
625def p_context(p):
626    '''context : security_context
627               | gen_context
628    '''
629    p[0] = p[1]
630
631def p_initial_sid(p):
632    '''initial_sid : SID IDENTIFIER context'''
633    s = refpolicy.InitialSid()
634    s.name = p[2]
635    s.context = p[3]
636    p[0] = s
637
638def p_genfscon(p):
639    '''genfscon : GENFSCON IDENTIFIER PATH context
640                | GENFSCON IDENTIFIER PATH MINUS IDENTIFIER context
641                | GENFSCON IDENTIFIER PATH MINUS MINUS context
642    '''
643    g = refpolicy.GenfsCon()
644    g.filesystem = p[2]
645    g.path = p[3]
646    if len(p) == 5:
647        g.context = p[4]
648    else:
649        g.context = p[6]
650
651    p[0] = g
652
653def p_fs_use(p):
654    '''fs_use : FS_USE_XATTR IDENTIFIER context SEMI
655              | FS_USE_TASK IDENTIFIER context SEMI
656              | FS_USE_TRANS IDENTIFIER context SEMI
657    '''
658    f = refpolicy.FilesystemUse()
659    if p[1] == "fs_use_xattr":
660        f.type = refpolicy.FilesystemUse.XATTR
661    elif p[1] == "fs_use_task":
662        f.type = refpolicy.FilesystemUse.TASK
663    elif p[1] == "fs_use_trans":
664        f.type = refpolicy.FilesystemUse.TRANS
665
666    f.filesystem = p[2]
667    f.context = p[3]
668
669    p[0] = f
670
671def p_portcon(p):
672    '''portcon : PORTCON IDENTIFIER NUMBER context
673               | PORTCON IDENTIFIER NUMBER MINUS NUMBER context'''
674    c = refpolicy.PortCon()
675    c.port_type = p[2]
676    if len(p) == 5:
677        c.port_number = p[3]
678        c.context = p[4]
679    else:
680        c.port_number = p[3] + "-" + p[4]
681        c.context = p[5]
682
683    p[0] = c
684
685def p_nodecon(p):
686    '''nodecon : NODECON NUMBER NUMBER context
687               | NODECON IPV6_ADDR IPV6_ADDR context
688    '''
689    n = refpolicy.NodeCon()
690    n.start = p[2]
691    n.end = p[3]
692    n.context = p[4]
693
694    p[0] = n
695
696def p_netifcon(p):
697    'netifcon : NETIFCON IDENTIFIER context context'
698    n = refpolicy.NetifCon()
699    n.interface = p[2]
700    n.interface_context = p[3]
701    n.packet_context = p[4]
702
703    p[0] = n
704
705def p_pirqcon(p):
706    'pirqcon : PIRQCON NUMBER context'
707    c = refpolicy.PirqCon()
708    c.pirq_number = p[2]
709    c.context = p[3]
710
711    p[0] = c
712
713def p_iomemcon(p):
714    '''iomemcon : IOMEMCON NUMBER context
715                | IOMEMCON NUMBER MINUS NUMBER context'''
716    c = refpolicy.IomemCon()
717    if len(p) == 4:
718        c.device_mem = p[2]
719        c.context = p[3]
720    else:
721        c.device_mem = p[2] + "-" + p[3]
722        c.context = p[4]
723
724    p[0] = c
725
726def p_ioportcon(p):
727    '''ioportcon : IOPORTCON NUMBER context
728                | IOPORTCON NUMBER MINUS NUMBER context'''
729    c = refpolicy.IoportCon()
730    if len(p) == 4:
731        c.ioport = p[2]
732        c.context = p[3]
733    else:
734        c.ioport = p[2] + "-" + p[3]
735        c.context = p[4]
736
737    p[0] = c
738
739def p_pcidevicecon(p):
740    'pcidevicecon : PCIDEVICECON NUMBER context'
741    c = refpolicy.PciDeviceCon()
742    c.device = p[2]
743    c.context = p[3]
744
745    p[0] = c
746
747def p_devicetreecon(p):
748    'devicetreecon : DEVICETREECON NUMBER context'
749    c = refpolicy.DevicetTeeCon()
750    c.path = p[2]
751    c.context = p[3]
752
753    p[0] = c
754
755def p_mls_range_def(p):
756    '''mls_range_def : mls_level_def MINUS mls_level_def
757                     | mls_level_def
758    '''
759    p[0] = p[1]
760    if len(p) > 2:
761        p[0] = p[0] + "-" + p[3]
762
763def p_mls_level_def(p):
764    '''mls_level_def : IDENTIFIER COLON comma_list
765                     | IDENTIFIER
766    '''
767    p[0] = p[1]
768    if len(p) > 2:
769        p[0] = p[0] + ":" + ",".join(p[3])
770
771def p_type_def(p):
772    '''type_def : TYPE IDENTIFIER COMMA comma_list SEMI
773                | TYPE IDENTIFIER SEMI
774                | TYPE IDENTIFIER ALIAS names SEMI
775                | TYPE IDENTIFIER ALIAS names COMMA comma_list SEMI
776    '''
777    t = refpolicy.Type(p[2])
778    if len(p) == 6:
779        if p[3] == ',':
780            t.attributes.update(p[4])
781        else:
782            t.aliases = p[4]
783    elif len(p) > 4:
784        t.aliases = p[4]
785        if len(p) == 8:
786            t.attributes.update(p[6])
787    p[0] = t
788
789def p_attribute_def(p):
790    'attribute_def : ATTRIBUTE IDENTIFIER SEMI'
791    a = refpolicy.Attribute(p[2])
792    p[0] = a
793
794def p_attribute_role_def(p):
795    'attribute_role_def : ATTRIBUTE_ROLE IDENTIFIER SEMI'
796    a = refpolicy.Attribute_Role(p[2])
797    p[0] = a
798
799def p_typealias_def(p):
800    'typealias_def : TYPEALIAS IDENTIFIER ALIAS names SEMI'
801    t = refpolicy.TypeAlias()
802    t.type = p[2]
803    t.aliases = p[4]
804    p[0] = t
805
806def p_role_def(p):
807    '''role_def : ROLE IDENTIFIER TYPES comma_list SEMI
808                | ROLE IDENTIFIER SEMI'''
809    r = refpolicy.Role()
810    r.role = p[2]
811    if len(p) > 4:
812        r.types.update(p[4])
813    p[0] = r
814
815def p_role_allow(p):
816    'role_allow : ALLOW names names SEMI'
817    r = refpolicy.RoleAllow()
818    r.src_roles = p[2]
819    r.tgt_roles = p[3]
820    p[0] = r
821
822def p_permissive(p):
823    'permissive : PERMISSIVE names SEMI'
824    pass
825
826def p_avrule_def(p):
827    '''avrule_def : ALLOW names names COLON names names SEMI
828                  | DONTAUDIT names names COLON names names SEMI
829                  | AUDITALLOW names names COLON names names SEMI
830                  | NEVERALLOW names names COLON names names SEMI
831    '''
832    a = refpolicy.AVRule()
833    if p[1] == 'dontaudit':
834        a.rule_type = refpolicy.AVRule.DONTAUDIT
835    elif p[1] == 'auditallow':
836        a.rule_type = refpolicy.AVRule.AUDITALLOW
837    elif p[1] == 'neverallow':
838        a.rule_type = refpolicy.AVRule.NEVERALLOW
839    a.src_types = p[2]
840    a.tgt_types = p[3]
841    a.obj_classes = p[5]
842    a.perms = p[6]
843    p[0] = a
844
845def p_typerule_def(p):
846    '''typerule_def : TYPE_TRANSITION names names COLON names IDENTIFIER SEMI
847                    | TYPE_TRANSITION names names COLON names IDENTIFIER FILENAME SEMI
848                    | TYPE_TRANSITION names names COLON names IDENTIFIER IDENTIFIER SEMI
849                    | TYPE_CHANGE names names COLON names IDENTIFIER SEMI
850                    | TYPE_MEMBER names names COLON names IDENTIFIER SEMI
851    '''
852    t = refpolicy.TypeRule()
853    if p[1] == 'type_change':
854        t.rule_type = refpolicy.TypeRule.TYPE_CHANGE
855    elif p[1] == 'type_member':
856        t.rule_type = refpolicy.TypeRule.TYPE_MEMBER
857    t.src_types = p[2]
858    t.tgt_types = p[3]
859    t.obj_classes = p[5]
860    t.dest_type = p[6]
861    t.file_name = p[7]
862    p[0] = t
863
864def p_typebound_def(p):
865    '''typebound_def : TYPEBOUNDS IDENTIFIER comma_list SEMI'''
866    t = refpolicy.TypeBound()
867    t.type = p[2]
868    t.tgt_types.update(p[3])
869    p[0] = t
870
871def p_bool(p):
872    '''bool : BOOL IDENTIFIER TRUE SEMI
873            | BOOL IDENTIFIER FALSE SEMI'''
874    b = refpolicy.Bool()
875    b.name = p[2]
876    if p[3] == "true":
877        b.state = True
878    else:
879        b.state = False
880    p[0] = b
881
882def p_gen_tunable(p):
883    '''gen_tunable : GEN_TUNABLE OPAREN IDENTIFIER COMMA TRUE CPAREN
884                   | GEN_TUNABLE OPAREN IDENTIFIER COMMA FALSE CPAREN
885                   | GEN_TUNABLE OPAREN TICK IDENTIFIER SQUOTE COMMA TRUE CPAREN
886                   | GEN_TUNABLE OPAREN TICK IDENTIFIER SQUOTE COMMA FALSE CPAREN'''
887    b = refpolicy.Bool()
888    if len(p) == 7:
889        id_pos = 3
890        state_pos = 5
891    else:
892        id_pos = 4
893        state_pos = 7
894    b.name = p[id_pos]
895    if p[state_pos] == "true":
896        b.state = True
897    else:
898        b.state = False
899    p[0] = b
900
901def p_conditional(p):
902    ''' conditional : IF OPAREN cond_expr CPAREN OBRACE interface_stmts CBRACE
903                    | IF OPAREN cond_expr CPAREN OBRACE interface_stmts CBRACE ELSE OBRACE interface_stmts CBRACE
904    '''
905    c = refpolicy.Conditional()
906    c.cond_expr = p[3]
907    collect(p[6], c, val=True)
908    if len(p) > 8:
909        collect(p[10], c, val=False)
910    p[0] = [c]
911
912def p_typeattribute_def(p):
913    '''typeattribute_def : TYPEATTRIBUTE IDENTIFIER comma_list SEMI'''
914    t = refpolicy.TypeAttribute()
915    t.type = p[2]
916    t.attributes.update(p[3])
917    p[0] = t
918
919def p_roleattribute_def(p):
920    '''roleattribute_def : ROLEATTRIBUTE IDENTIFIER comma_list SEMI'''
921    t = refpolicy.RoleAttribute()
922    t.role = p[2]
923    t.roleattributes.update(p[3])
924    p[0] = t
925
926def p_range_transition_def(p):
927    '''range_transition_def : RANGE_TRANSITION names names COLON names mls_range_def SEMI
928                            | RANGE_TRANSITION names names names SEMI'''
929    pass
930
931def p_role_transition_def(p):
932    '''role_transition_def : ROLE_TRANSITION names names names SEMI'''
933    pass
934
935def p_cond_expr(p):
936    '''cond_expr : IDENTIFIER
937                 | EXPL cond_expr
938                 | cond_expr AMP AMP cond_expr
939                 | cond_expr BAR BAR cond_expr
940                 | cond_expr EQUAL EQUAL cond_expr
941                 | cond_expr EXPL EQUAL cond_expr
942    '''
943    l = len(p)
944    if l == 2:
945        p[0] = [p[1]]
946    elif l == 3:
947        p[0] = [p[1]] + p[2]
948    else:
949        p[0] = p[1] + [p[2] + p[3]] + p[4]
950
951
952#
953# Basic terminals
954#
955
956# Identifiers and lists of identifiers. These must
957# be handled somewhat gracefully. Names returns an IdSet and care must
958# be taken that this is _assigned_ to an object to correctly update
959# all of the flags (as opposed to using update). The other terminals
960# return list - this is to preserve ordering if it is important for
961# parsing (for example, interface_call must retain the ordering). Other
962# times the list should be used to update an IdSet.
963
964def p_names(p):
965    '''names : identifier
966             | nested_id_set
967             | asterisk
968             | TILDE identifier
969             | TILDE nested_id_set
970             | IDENTIFIER MINUS IDENTIFIER
971    '''
972    s = refpolicy.IdSet()
973    if len(p) < 3:
974        expand(p[1], s)
975    elif len(p) == 3:
976        expand(p[2], s)
977        s.compliment = True
978    else:
979        expand([p[1]])
980        s.add("-" + p[3])
981    p[0] = s
982
983def p_identifier(p):
984    'identifier : IDENTIFIER'
985    p[0] = [p[1]]
986
987def p_asterisk(p):
988    'asterisk : ASTERISK'
989    p[0] = [p[1]]
990
991def p_nested_id_set(p):
992    '''nested_id_set : OBRACE nested_id_list CBRACE
993    '''
994    p[0] = p[2]
995
996def p_nested_id_list(p):
997    '''nested_id_list : nested_id_element
998                      | nested_id_list nested_id_element
999    '''
1000    if len(p) == 2:
1001        p[0] = p[1]
1002    else:
1003        p[0] = p[1] + p[2]
1004
1005def p_nested_id_element(p):
1006    '''nested_id_element : identifier
1007                         | MINUS IDENTIFIER
1008                         | nested_id_set
1009    '''
1010    if len(p) == 2:
1011        p[0] = p[1]
1012    else:
1013        # For now just leave the '-'
1014        str = "-" + p[2]
1015        p[0] = [str]
1016
1017def p_comma_list(p):
1018    '''comma_list : nested_id_list
1019                  | comma_list COMMA nested_id_list
1020    '''
1021    if len(p) > 2:
1022        p[1] = p[1] + p[3]
1023    p[0] = p[1]
1024
1025def p_optional_semi(p):
1026    '''optional_semi : SEMI
1027                   | empty'''
1028    pass
1029
1030
1031#
1032# Interface to the parser
1033#
1034
1035def p_error(tok):
1036    global error, parse_file, success, parser
1037    error = "%s: Syntax error on line %d %s [type=%s]" % (parse_file, tok.lineno, tok.value, tok.type)
1038    print(error)
1039    success = False
1040
1041def prep_spt(spt):
1042    if not spt:
1043        return { }
1044    map = {}
1045    for x in spt:
1046        map[x.name] = x
1047
1048parser = None
1049lexer = None
1050def create_globals(module, support, debug):
1051    global parser, lexer, m, spt
1052
1053    if not parser:
1054        lexer = lex.lex()
1055        parser = yacc.yacc(method="LALR", debug=debug, write_tables=0)
1056
1057    if module is not None:
1058        m = module
1059    else:
1060        m = refpolicy.Module()
1061
1062    if not support:
1063        spt = refpolicy.SupportMacros()
1064    else:
1065        spt = support
1066
1067def parse(text, module=None, support=None, debug=False):
1068    create_globals(module, support, debug)
1069    global error, parser, lexer, success
1070
1071    lexer.lineno = 1
1072    success = True
1073
1074    try:
1075        parser.parse(text, debug=debug, lexer=lexer)
1076    except Exception as e:
1077        parser = None
1078        lexer = None
1079        error = "internal parser error: %s" % str(e) + "\n" + traceback.format_exc()
1080
1081    if not success:
1082        # force the parser and lexer to be rebuilt - we have some problems otherwise
1083        parser = None
1084        msg = 'could not parse text: "%s"' % error
1085        raise ValueError(msg)
1086    return m
1087
1088def list_headers(root):
1089    modules = []
1090    support_macros = None
1091
1092    for dirpath, dirnames, filenames in os.walk(root):
1093        for name in filenames:
1094            modname = os.path.splitext(name)
1095            filename = os.path.join(dirpath, name)
1096
1097            if modname[1] == '.spt':
1098                if name == "obj_perm_sets.spt":
1099                    support_macros = filename
1100                elif len(re.findall("patterns", modname[0])):
1101                    modules.append((modname[0], filename))
1102            elif modname[1] == '.if':
1103                modules.append((modname[0], filename))
1104
1105    return (modules, support_macros)
1106
1107
1108def parse_headers(root, output=None, expand=True, debug=False):
1109    from . import util
1110
1111    headers = refpolicy.Headers()
1112
1113    modules = []
1114    support_macros = None
1115
1116    if os.path.isfile(root):
1117        name = os.path.split(root)[1]
1118        if name == '':
1119            raise ValueError("Invalid file name %s" % root)
1120        modname = os.path.splitext(name)
1121        modules.append((modname[0], root))
1122        all_modules, support_macros = list_headers(defaults.headers())
1123    else:
1124        modules, support_macros = list_headers(root)
1125
1126    if expand and not support_macros:
1127        raise ValueError("could not find support macros (obj_perm_sets.spt)")
1128
1129    def o(msg):
1130        if output:
1131            output.write(msg)
1132
1133    def parse_file(f, module, spt=None):
1134        global parse_file
1135        if debug:
1136            o("parsing file %s\n" % f)
1137        try:
1138            fd = open(f)
1139            txt = fd.read()
1140            fd.close()
1141            parse_file = f
1142            parse(txt, module, spt, debug)
1143        except IOError as e:
1144            return
1145        except ValueError as e:
1146            raise ValueError("error parsing file %s: %s" % (f, str(e)))
1147
1148    spt = None
1149    if support_macros:
1150        o("Parsing support macros (%s): " % support_macros)
1151        spt = refpolicy.SupportMacros()
1152        parse_file(support_macros, spt)
1153
1154        headers.children.append(spt)
1155
1156        # FIXME: Total hack - add in can_exec rather than parse the insanity
1157        # of misc_macros. We are just going to pretend that this is an interface
1158        # to make the expansion work correctly.
1159        can_exec = refpolicy.Interface("can_exec")
1160        av = access.AccessVector(["$1","$2","file","execute_no_trans","open", "read",
1161                                  "getattr","lock","execute","ioctl"])
1162
1163        can_exec.children.append(refpolicy.AVRule(av))
1164        headers.children.append(can_exec)
1165
1166        o("done.\n")
1167
1168    if output and not debug:
1169        status = util.ConsoleProgressBar(sys.stdout, steps=len(modules))
1170        status.start("Parsing interface files")
1171
1172    failures = []
1173    for x in modules:
1174        m = refpolicy.Module()
1175        m.name = x[0]
1176        try:
1177            if expand:
1178                parse_file(x[1], m, spt)
1179            else:
1180                parse_file(x[1], m)
1181        except ValueError as e:
1182            o(str(e) + "\n")
1183            failures.append(x[1])
1184            continue
1185
1186        headers.children.append(m)
1187        if output and not debug:
1188            status.step()
1189
1190    if len(failures):
1191        o("failed to parse some headers: %s\n" % ", ".join(failures))
1192
1193    return headers
1194