1 import uuid
2 from dataclasses import astuple, dataclass
3 from typing import Generator, MutableSet, Optional, TypeVar, Union
4
5 from lxml import etree
6 from lxml.etree import _Element
7
8 from pcs.common.types import StringCollection
9 from pcs.common.validate import is_integer
10
11 T = TypeVar("T", bound=type)
12
13
14 def bin_to_str(binary: bytes) -> str:
15 return "".join(map(chr, binary))
16
17
18 def get_all_subclasses(cls: T) -> MutableSet[T]:
19 subclasses = set(cls.__subclasses__())
20 return subclasses.union(
21 {s for c in subclasses for s in get_all_subclasses(c)}
22 )
23
24
25 def get_unique_uuid(already_used: StringCollection) -> str:
26 is_duplicate = True
27 while is_duplicate:
28 candidate = str(uuid.uuid4())
29 is_duplicate = candidate in already_used
30 return candidate
31
32
33 def format_os_error(e: OSError) -> str:
34 if e.filename:
35 return f"{e.strerror}: '{e.filename}'"
36 if e.strerror:
37 return e.strerror
38 return (f"{e.__class__.__name__} {e}").strip()
39
40
41 def xml_fromstring(xml: str) -> _Element:
42 # If the xml contains encoding declaration such as:
43 # <?xml version="1.0" encoding="UTF-8"?>
44 # we get an exception in python3:
45 # ValueError: Unicode strings with encoding declaration are not supported.
46 # Please use bytes input or XML fragments without declaration.
47 # So we encode the string to bytes.
48 return etree.fromstring(
49 xml.encode("utf-8"),
50 # it raises on a huge xml without the flag huge_tree=True
51 # see https://bugzilla.redhat.com/show_bug.cgi?id=1506864
|
(1) Event Sigma main event: |
The Python application enables entity expansion by setting the `lxml.etree.XMLParser` value `resolve_entities` to `true` or `internal` (the default value). If untrusted XML is parsed with entity expansion enabled, a malicious attacker could submit a document that contains very deeply nested entity definitions (known as a Billion Laughs Attack), causing the parser to use large amounts of memory and processing power resulting in a denial of service (DoS) condition. |
|
(2) Event remediation: |
Explicitly set `resolve_entities` argument to `False`. |
52 etree.XMLParser(huge_tree=True),
53 )
54
55
56 def timeout_to_seconds(timeout: Union[int, str]) -> Optional[int]:
57 """
58 Transform pacemaker style timeout to number of seconds. If `timeout` is not
59 a valid timeout, `None` is returned.
60
61 timeout -- timeout string
62 """
63 try:
64 candidate = int(timeout)
65 if candidate >= 0:
66 return candidate
67 return None
68 except ValueError:
69 pass
70 # Now we know the timeout is not an integer nor an integer string.
71 # Let's make sure mypy knows the timeout is a string as well.
72 timeout = str(timeout)
73 suffix_multiplier = {
74 "s": 1,
75 "sec": 1,
76 "m": 60,
77 "min": 60,
78 "h": 3600,
79 "hr": 3600,
80 }
81 for suffix, multiplier in suffix_multiplier.items():
82 if timeout.endswith(suffix):
83 candidate2 = timeout[: -len(suffix)]
84 if is_integer(candidate2, at_least=0):
85 return int(candidate2) * multiplier
86 return None
87
88
89 @dataclass(frozen=True)
90 class Version:
91 major: int
92 minor: Optional[int] = None
93 revision: Optional[int] = None
94
95 @property
96 def as_full_tuple(self) -> tuple[int, int, int]:
97 return (
98 self.major,
99 self.minor if self.minor is not None else 0,
100 self.revision if self.revision is not None else 0,
101 )
102
103 def normalize(self) -> "Version":
104 return self.__class__(*self.as_full_tuple)
105
106 def __iter__(self) -> Generator[Optional[int], None, None]:
107 yield from astuple(self)
108
109 def __getitem__(self, index: int) -> Optional[int]:
110 return astuple(self)[index]
111
112 def __str__(self) -> str:
113 return ".".join([str(x) for x in self if x is not None])
114
115 def __lt__(self, other: "Version") -> bool:
116 return self.as_full_tuple < other.as_full_tuple
117
118 def __le__(self, other: "Version") -> bool:
119 return self.as_full_tuple <= other.as_full_tuple
120
121 def __hash__(self) -> int:
122 # https://docs.astral.sh/ruff/rules/eq-without-hash/
123 # used self.as_full_tuple because __eq__ and __hash__ should be in sync,
124 # objects which compare equal must have the same hash value
125 return hash(self.as_full_tuple)
126
127 # See, https://stackoverflow.com/questions/37557411/why-does-defining-the-argument-types-for-eq-throw-a-mypy-type-error
128 def __eq__(self, other: object) -> bool:
129 if not isinstance(other, Version):
130 return NotImplemented
131 return self.as_full_tuple == other.as_full_tuple
132
133 def __ne__(self, other: object) -> bool:
134 if not isinstance(other, Version):
135 return NotImplemented
136 return self.as_full_tuple != other.as_full_tuple
137
138 def __gt__(self, other: "Version") -> bool:
139 return self.as_full_tuple > other.as_full_tuple
140
141 def __ge__(self, other: "Version") -> bool:
142 return self.as_full_tuple >= other.as_full_tuple
143