diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rwxr-xr-x | contrib/fix_private-bin.py | 164 |
2 files changed, 98 insertions, 70 deletions
diff --git a/.gitignore b/.gitignore index 629a86d25..9995da44c 100644 --- a/.gitignore +++ b/.gitignore | |||
@@ -42,4 +42,6 @@ seccomp.block_secondary | |||
42 | seccomp.mdwx | 42 | seccomp.mdwx |
43 | src/common.mk | 43 | src/common.mk |
44 | aclocal.m4 | 44 | aclocal.m4 |
45 | 45 | __pycache__ | |
46 | *.pyc | ||
47 | *.pyo | ||
diff --git a/contrib/fix_private-bin.py b/contrib/fix_private-bin.py index c6c6f908d..668d68ff2 100755 --- a/contrib/fix_private-bin.py +++ b/contrib/fix_private-bin.py | |||
@@ -1,5 +1,4 @@ | |||
1 | #!/usr/bin/python3 | 1 | #!/usr/bin/python3 |
2 | |||
3 | __author__ = "KOLANICH" | 2 | __author__ = "KOLANICH" |
4 | __copyright__ = """This is free and unencumbered software released into the public domain. | 3 | __copyright__ = """This is free and unencumbered software released into the public domain. |
5 | 4 | ||
@@ -27,122 +26,149 @@ OTHER DEALINGS IN THE SOFTWARE. | |||
27 | For more information, please refer to <https://unlicense.org/>""" | 26 | For more information, please refer to <https://unlicense.org/>""" |
28 | __license__ = "Unlicense" | 27 | __license__ = "Unlicense" |
29 | 28 | ||
30 | import sys, os, glob, re | 29 | import typing |
30 | import sys, os, re | ||
31 | from collections import OrderedDict | ||
32 | from pathlib import Path | ||
33 | from shutil import which | ||
31 | 34 | ||
32 | privRx = re.compile("^(?:#\s*)?private-bin") | 35 | privRx = re.compile(r"^(#\s*)?(private-bin)(\s+)(.+)$") |
33 | 36 | ||
34 | 37 | ||
35 | def fixSymlinkedBins(files, replMap): | 38 | def fixSymlinkedBins(files: typing.List[Path], replMap: typing.Dict[str, str]) -> None: |
39 | """ | ||
40 | Used to add filenames to private-bin directives of files if the ones present are mentioned in replMap | ||
41 | replMap is a dict where key is the marker filename and value is the filename to add | ||
36 | """ | 42 | """ |
37 | Used to add filenames to private-bin directives of files if the ones present are mentioned in replMap | ||
38 | replMap is a dict where key is the marker filename and value is the filename to add | ||
39 | """ | ||
40 | |||
41 | rxs = dict() | ||
42 | for (old, new) in replMap.items(): | ||
43 | rxs[old] = re.compile("\\b" + old + "\\b") | ||
44 | rxs[new] = re.compile("\\b" + new + "\\b") | ||
45 | #print(rxs) | ||
46 | 43 | ||
47 | for filename in files: | 44 | for filename in files: |
48 | lines = None | 45 | lines = filename.read_text(encoding="utf-8").split("\n") |
49 | with open(filename, "r") as file: | ||
50 | lines = file.readlines() | ||
51 | 46 | ||
52 | shouldUpdate = False | 47 | shouldUpdate = False |
53 | for (i, line) in enumerate(lines): | 48 | for (i, line) in enumerate(lines): |
54 | if privRx.search(line): | 49 | m = privRx.match(line) |
50 | if m: | ||
51 | lineUpdated = False | ||
52 | mBins = OrderedDict((sb, sb) for sb in (b.strip() for b in m.group(4).split(","))) | ||
53 | |||
55 | for (old, new) in replMap.items(): | 54 | for (old, new) in replMap.items(): |
56 | if rxs[old].search(line) and not rxs[new].search(line): | 55 | if old in mBins: |
57 | lines[i] = rxs[old].sub(old + "," + new, line) | 56 | #print(old, "->", new) |
58 | shouldUpdate = True | 57 | if new not in mBins: |
59 | print(lines[i]) | 58 | mBins[old] = old + "," + new |
59 | lineUpdated = True | ||
60 | |||
61 | if lineUpdated: | ||
62 | comment = m.group(1) | ||
63 | if comment is None: | ||
64 | comment = "" | ||
65 | lines[i] = comment + m.group(2) + m.group(3) + ",".join(mBins.values()) | ||
66 | shouldUpdate = True | ||
60 | 67 | ||
61 | if shouldUpdate: | 68 | if shouldUpdate: |
62 | with open(filename, "w") as file: | 69 | filename.write_text("\n".join(lines), encoding="utf-8") |
63 | file.writelines(lines) | ||
64 | 70 | ||
65 | 71 | ||
66 | def createSetOfBinaries(files): | 72 | def createSetOfBinaries(files: typing.List[Path]) -> typing.Set[str]: |
73 | """ | ||
74 | Creates a set of binaries mentioned in private-bin directives of files. | ||
67 | """ | 75 | """ |
68 | Creates a set of binaries mentioned in private-bin directives of files. | ||
69 | """ | ||
70 | s = set() | 76 | s = set() |
71 | for filename in files: | 77 | for filename in files: |
72 | with open(filename, "r") as file: | 78 | with open(filename, "r") as file: |
73 | for line in file: | 79 | for line in file: |
74 | if privRx.search(line): | 80 | m = privRx.match(line) |
75 | bins = line.split(",") | 81 | if m: |
76 | bins[0] = bins[0].split(" ")[-1] | 82 | bins = m.group(4).split(",") |
77 | bins = [n.strip() for n in bins] | 83 | bins = [n.strip() for n in bins] |
78 | s = s | set(bins) | 84 | s = s | set(bins) |
79 | return s | 85 | return s |
80 | 86 | ||
87 | def getExecutableNameFromLink(p: Path) -> str: | ||
88 | return os.readlink(str(p)).split(" ")[0] | ||
89 | |||
90 | |||
91 | forbiddenExecutables= ["firejail"] | ||
92 | |||
93 | def populateForbiddenExecutables(): | ||
94 | forbiddenSymlinks = [] | ||
95 | for e in forbiddenExecutables: | ||
96 | r = which(e) | ||
97 | if r is not None: | ||
98 | yield r | ||
99 | |||
100 | forbiddenSymlinks = set(populateForbiddenExecutables()) | ||
81 | 101 | ||
82 | def createSymlinkTable(binDirs, binariesSet): | 102 | |
103 | def createSymlinkTable(binDirs: typing.Iterable[Path], binariesSet: typing.Set[str]) -> typing.Mapping[str, str]: | ||
104 | """ | ||
105 | creates a dict of symlinked binaries in the system where a key is a symlink name and value is a symlinked binary. | ||
106 | binDirs are folders to look into for binaries symlinks | ||
107 | binariesSet is a set of binaries to be checked if they are actually a symlinks | ||
83 | """ | 108 | """ |
84 | creates a dict of symlinked binaries in the system where a key is a symlink name and value is a symlinked binary. | ||
85 | binDirs are folders to look into for binaries symlinks | ||
86 | binariesSet is a set of binaries to be checked if they are actually a symlinks | ||
87 | """ | ||
88 | m = dict() | 109 | m = dict() |
89 | toProcess = binariesSet | 110 | toProcess = binariesSet |
90 | while len(toProcess) != 0: | 111 | while len(toProcess) != 0: |
91 | additional = set() | 112 | additional = set() |
92 | for sh in toProcess: | 113 | for binName in toProcess: |
93 | for bD in binDirs: | 114 | for binaryDir in binDirs: |
94 | p = bD + os.path.sep + sh | 115 | p = binaryDir / binName |
95 | if os.path.exists(p): | 116 | if p.is_symlink(): |
96 | if os.path.islink(p): | 117 | res = [] |
97 | m[sh] = os.readlink(p) | 118 | nm = getExecutableNameFromLink(p) |
98 | additional.add(m[sh].split(" ")[0]) | 119 | if nm in forbiddenSymlinks: |
99 | else: | 120 | continue |
100 | pass | 121 | m[binName] = nm |
122 | additional.add(nm) | ||
101 | break | 123 | break |
124 | |||
102 | toProcess = additional | 125 | toProcess = additional |
103 | return m | 126 | return m |
104 | 127 | ||
105 | 128 | ||
106 | def doTheFixes(profilesPath, binDirs): | 129 | def doTheFixes(profilesPath: Path, binDirs: typing.Iterable[Path]) -> None: |
107 | """ | 130 | """ |
108 | Fixes private-bin in .profiles for firejail. The pipeline is as follows: | 131 | Fixes private-bin in .profiles for firejail. The pipeline is as follows: |
109 | discover files -> discover mentioned binaries -> | 132 | discover files -> discover mentioned binaries -> |
110 | discover the ones which are symlinks -> | 133 | discover the ones which are symlinks -> |
111 | make a look-up table for fix -> | 134 | make a look-up table for fix -> |
112 | filter the ones can be fixed (we cannot fix the ones which are not in directories for binaries) -> | 135 | filter the ones can be fixed (we cannot fix the ones which are not in directories for binaries) -> |
113 | apply fix | 136 | apply fix |
114 | """ | 137 | """ |
115 | files = glob.glob(profilesPath + os.path.sep + "*.profile") | 138 | files = list(profilesPath.glob("**/*.profile")) |
116 | bins = createSetOfBinaries(files) | 139 | bins = createSetOfBinaries(files) |
117 | #print("The binaries used are:") | 140 | #print("The binaries used are:") |
118 | #print(bins) | 141 | #print(bins) |
119 | stbl = createSymlinkTable(binDirs, bins) | 142 | stbl = createSymlinkTable(binDirs, bins) |
120 | print("The replacement table is:") | 143 | print("The replacement table is:") |
121 | print(stbl) | 144 | print(stbl) |
122 | stbl = { | 145 | for k, v in tuple(stbl.items()): |
123 | a[0]: a[1] | 146 | if k.find(os.path.sep) < 0 and v.find(os.path.sep) < 0: |
124 | for a in stbl.items() | 147 | pass |
125 | if a[0].find(os.path.sep) < 0 and a[1].find(os.path.sep) < 0 | 148 | else: |
126 | } | 149 | del stbl[k] |
150 | |||
127 | print("Filtered replacement table is:") | 151 | print("Filtered replacement table is:") |
128 | print(stbl) | 152 | print(stbl) |
129 | fixSymlinkedBins(files, stbl) | 153 | fixSymlinkedBins(files, stbl) |
130 | 154 | ||
131 | 155 | ||
156 | thisDir = Path(__file__).absolute().parent | ||
157 | defaultProfilesPath = (thisDir.parent / "etc") | ||
158 | |||
159 | |||
132 | def printHelp(): | 160 | def printHelp(): |
133 | print("python3 " + os.path.basename(__file__) + | 161 | print("python3 " + str(thisDir) + |
134 | " <dir with .profile files>\nThe default dir is " + | 162 | " <dir with .profile files>\nThe default dir is " + |
135 | defaultProfilesPath + "\n" + doTheFixes.__doc__) | 163 | str(defaultProfilesPath) + "\n" + doTheFixes.__doc__) |
136 | 164 | ||
137 | 165 | ||
138 | def main(): | 166 | def main() -> None: |
139 | """The main function. Parses the commandline args, shows messages and calles the function actually doing the work.""" | 167 | """The main function. Parses the commandline args, shows messages and calles the function actually doing the work.""" |
140 | print(repr(sys.argv)) | ||
141 | defaultProfilesPath = "../etc" | ||
142 | if len(sys.argv) > 2 or (len(sys.argv) == 2 and | 168 | if len(sys.argv) > 2 or (len(sys.argv) == 2 and |
143 | (sys.argv[1] == '-h' or sys.argv[1] == '--help')): | 169 | (sys.argv[1] == "-h" or sys.argv[1] == "--help")): |
144 | printHelp() | 170 | printHelp() |
145 | exit(1) | 171 | sys.exit(1) |
146 | 172 | ||
147 | profilesPath = None | 173 | profilesPath = None |
148 | if len(sys.argv) == 2: | 174 | if len(sys.argv) == 2: |
@@ -154,14 +180,14 @@ def main(): | |||
154 | else: | 180 | else: |
155 | print(sys.argv[1] + " does not exist") | 181 | print(sys.argv[1] + " does not exist") |
156 | printHelp() | 182 | printHelp() |
157 | exit(1) | 183 | sys.exit(1) |
158 | else: | 184 | else: |
159 | print("Using default profiles dir: " + defaultProfilesPath) | 185 | print("Using default profiles dir: ", defaultProfilesPath) |
160 | profilesPath = defaultProfilesPath | 186 | profilesPath = defaultProfilesPath |
161 | 187 | ||
162 | binDirs = [ | 188 | binDirs = ("/bin", "/usr/bin", "/usr/bin", "/usr/sbin", "/usr/local/bin", "/usr/local/sbin") |
163 | "/bin", "/usr/bin", "/usr/sbin", "/usr/local/bin", "/usr/local/sbin" | 189 | binDirs = type(binDirs)(Path(p) for p in binDirs) |
164 | ] | 190 | |
165 | print("Binaries dirs are:") | 191 | print("Binaries dirs are:") |
166 | print(binDirs) | 192 | print(binDirs) |
167 | doTheFixes(profilesPath, binDirs) | 193 | doTheFixes(profilesPath, binDirs) |