Merge ~cache-use-only/ubuntu-cve-tracker:kcve-merge into ubuntu-cve-tracker:master

Proposed by Yuxuan Luo
Status: Merged
Merged at revision: b1ce336199e0992a8a4e2488b30e12b4cb8caef4
Proposed branch: ~cache-use-only/ubuntu-cve-tracker:kcve-merge
Merge into: ubuntu-cve-tracker:master
Diff against target: 313 lines (+307/-0)
1 file modified
scripts/kcve-merge (+307/-0)
Reviewer Review Type Date Requested Status
Seth Arnold Approve
Review via email: mp+450275@code.launchpad.net

Commit message

scripts: add kcve-merge

Adds a Python3 script to facilitate merging designated kernel CVE
changes from autotriage branch to upstream security branch.

Signed-off-by: Yuxuan Luo <email address hidden>

Description of the change

Examples:

```bash
$ ./kcve-merge --cve CVE-2023-4273 CVE-2023-2235 --to 'pending.*'
$ ./kcve-merge --kernel jammy_linux-azure devel_linux-aws --cve CVE-2023-4273 CVE-2023-2235 --to 'pending.*'
$ ./kcve-merge --cve-list ./cve_list.txt
$ ./kcve-merge --cve-list ./cve_list.txt --from 'ignored'
```

To post a comment you must log in.
Revision history for this message
Seth Arnold (seth-arnold) wrote :

Thanks for putting this together, this looks like a nice way to work with smaller pieces of the kernel triage process. I've got a few comments inline; my thoughts aren't necessarily something you need to do, but I do hope it's useful.

Revision history for this message
Yuxuan Luo (cache-use-only) wrote :

> Thanks for putting this together, this looks like a nice way to work with
> smaller pieces of the kernel triage process. I've got a few comments inline;
> my thoughts aren't necessarily something you need to do, but I do hope it's
> useful.
Thank you for your intuitive and comprehensive review! I have force pushed a newer version with additional notes in it, hope it helps with future maintenance.

Revision history for this message
Seth Arnold (seth-arnold) wrote :

Thanks so much for the updates, I'm finding this newer version easier to read and understand.

Now that I understand it better, I've got some more thoughts :) as before, they're not strictly things you have to do, but I do hope that they lead to a better end result.

Thanks

Revision history for this message
Yuxuan Luo (cache-use-only) wrote :

> Thanks so much for the updates, I'm finding this newer version easier to read
> and understand.
>
> Now that I understand it better, I've got some more thoughts :) as before,
> they're not strictly things you have to do, but I do hope that they lead to a
> better end result.
>
> Thanks

I took your advice and re-structured the script so that it should be much easier to read. As always, thanks for sharing your thoughts!

Revision history for this message
Seth Arnold (seth-arnold) wrote :

Hi Yuxuan, this looks great! Thanks for iterating on it with me.

review: Approve
Revision history for this message
Eduardo Barretto (ebarretto) wrote :

Marked this as merged in b1ce336199e0992a8a4e2488b30e12b4cb8caef4

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/scripts/kcve-merge b/scripts/kcve-merge
0new file mode 1007550new file mode 100755
index 0000000..9897b1d
--- /dev/null
+++ b/scripts/kcve-merge
@@ -0,0 +1,307 @@
1#!/usr/bin/env python3
2#
3# Merge autotriage branch to master
4#
5
6import os
7import re
8import sys
9import pathlib
10import argparse
11import fileinput
12import subprocess
13
14AUTOBRANCH = 'autotriage'
15SECBRANCH = 'master'
16
17def warn(prompt: str):
18 print("\033[93m" + prompt + "\033[0m")
19
20
21def error(prompt: str):
22 print("\033[91m" + prompt + "\033[0m")
23
24
25def info(prompt: str):
26 print("\033[94m" + prompt + "\033[0m")
27
28
29# Import cve_lib in UCT
30uct_path = os.environ.get('UCT')
31cwd = os.getcwd()
32try:
33 assert (uct_path is not None)
34 os.chdir(uct_path)
35 sys.path.append(uct_path + '/scripts')
36 import cve_lib
37except AssertionError:
38 error('Please configure $UCT in your shell source file')
39 error('Example: export UCT=/home/ubuntu/Documents/ubuntu-cve-tracker')
40 error('Current $UCT: ' + '{uct_path}')
41 sys.exit(1)
42except FileNotFoundError:
43 error('Could not change directory to: ' + uct_path)
44 sys.exit(1)
45except:
46 error('Error importing cve_lib')
47 sys.exit(1)
48
49
50def sanitize_cves(arg_cve: list[str], arg_cve_list: str) -> list[str]:
51 """
52 Sanitize and build a list of unique CVE IDs based on the given arguments
53
54 Parameters
55 ----------
56 - arg_cve: list[str]
57 - A list of CVEs
58 - Example: ['CVE-2023-1111', 'CVE-2022-2222']
59 - arg_cve_list: str
60 - Path to the file containing a list of CVE, one per line
61 - Example: ../cve_list.txt
62
63 Return
64 ------
65 - cve_path_list: list[str]
66 - A list of unique CVE IDs
67 - Example: ['CVE-2023-1111', 'CVE-2022-1234']
68 """
69
70 cve_set = set()
71
72 # If no --cve nor --cve-list is provided, selecting all kernel CVEs by default
73 if arg_cve is None and arg_cve_list is None:
74 cmd = ["git", "--no-pager", "diff", "--name-only", SECBRANCH+".."+AUTOBRANCH]
75
76 dump = subprocess.run(cmd, capture_output=True).stdout.decode()
77 for line in dump.splitlines():
78 # Skip non-CVE files
79 if "/CVE" not in line:
80 continue
81
82 cve_number = line.split(sep='/')[1]
83 cve_set.add(cve_number)
84 return list(cve_set)
85
86 if arg_cve is not None:
87 for cve_num in arg_cve:
88 cve_set.add(cve_num)
89
90 if arg_cve_list is not None:
91 file_path = arg_cve_list
92
93 # The cwd has been set to UCT for the sake of cve_lib;
94 # therefore, manually concat relative path to the calling
95 # work directory
96 if not os.path.isabs(arg_cve_list):
97 file_path = os.path.join(cwd, pathlib.Path(arg_cve_list))
98
99 try:
100 with open(file_path) as cve_file:
101 for line in cve_file.read().splitlines():
102 cve_set.add(cve_lib.find_cve(line))
103 except FileNotFoundError as e:
104 warn(e.strerror + ": " + file_path)
105 except Exception as e:
106 error("Unknown error when parsing " + file_path + ": " + e)
107
108 return list(cve_set)
109
110
111def build_path_list(cve_list: list[str]) -> list[str]:
112 """Build a list of absolute path to CVEs with the given CVEs
113
114 Parameters
115 ----------
116 - cve_list: list[str]
117 - A list of CVEs
118 - Example: ['CVE-2023-1111', 'CVE-2022-2222']
119
120 Return
121 ------
122 - cve_path_list: list[str]
123 - A list of absolute path to CVEs
124 - Example: ['/home/ubuntu-cve-tracker/active/CVE-2023-1111']
125 """
126 cve_path_list = []
127
128 for cve_num in cve_list:
129 try:
130 cve_path_list.append(cve_lib.find_cve(cve_num))
131 except ValueError:
132 warn("Cannot find given CVE number: " + cve_num)
133 continue
134
135 return cve_path_list
136
137
138def build_status_dict(cve_list: list[str]) -> {}:
139 """Build a dictionary of diffs
140
141 Parameter
142 ---------
143 - cve_list: list[str]
144 - A list of CVEs
145
146 Returns
147 -------
148 - A Python dictionary of status line changes
149 - {
150 CVE_path:{
151 kernel: status
152 }
153 }
154 - Example:
155 {
156 '/home/ubuntu/ubuntu-cve-tracker/active/CVE-2022-1111': {
157 'devel_linux': 'ignored (ESM criteria, was needed)',
158 'jammy_linux-aws' : 'pending',
159 },
160 '/home/ubuntu/ubuntu-cve-tracker/active/CVE-2022-1112': {
161 'focal_linux': 'needed',
162 }
163 }
164 """
165 status_line_re = re.compile(r"_linux.*: ")
166 to_status = {}
167
168 for cve_path in cve_list:
169 to_status[cve_path] = {}
170 cmd = ["git", "--no-pager", "diff", "--no-color", SECBRANCH+".."+AUTOBRANCH, "--", cve_path]
171
172 try:
173 dump = subprocess.run(cmd, capture_output=True).stdout.decode()
174 for line in dump.splitlines():
175 if status_line_re.search(line):
176 splitted = line[1::].split(sep=':')
177 if line[0] == '+' and line[1] != '+':
178 release = splitted[0]
179 status = splitted[1][1::] # get rid of the leading space
180 to_status[cve_path][release] = status
181 except OSError:
182 error("Error executing: " + ' '.join(cmd))
183 except:
184 error("Unknown error occurs when building status dictionary")
185
186 return to_status
187
188def main():
189 parser = argparse.ArgumentParser(
190 prog='kcve-merge',
191 description='Merge selective status change from autotriage branch to master branch',
192 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
193 )
194 parser.add_argument(
195 '--kernel',
196 action='extend',
197 help='Specify the kernel of CVEs to merge: RELEASE_SOURCE',
198 nargs='+',
199 )
200 parser.add_argument(
201 '--cve',
202 action='extend',
203 help='Specify the CVE to merge its status change: CVE-2023-3611',
204 nargs='+',
205 )
206 parser.add_argument(
207 '--from',
208 help='Filter the from status using regex',
209 )
210 parser.add_argument(
211 '--to',
212 help='Filter the to status using regex',
213 )
214 parser.add_argument(
215 '--cve-list',
216 help='Provide a CVE list: ./cve_priority.txt',
217 type=str
218 )
219 args = parser.parse_args()
220 arg_kernel: list[str] = args.kernel
221 arg_cve: list[str] = args.cve
222 arg_from = vars(args).get("from")
223 arg_to = args.to
224 arg_cve_list = args.cve_list
225
226 cve_list = []
227 cve_path_list = []
228 to_status = {}
229 status_line_re = re.compile(r"_linux.*: ")
230 if arg_from is not None:
231 try:
232 from_re = re.compile(arg_from)
233 except:
234 error("Error compiling from-status regex: " + arg_from)
235 exit(1)
236 if arg_to is not None:
237 try:
238 to_re = re.compile(arg_to)
239 except:
240 error("Error compiling to-status regex: " + arg_to)
241 exit(1)
242
243 curr_branch = subprocess.run("git rev-parse --abbrev-ref HEAD", shell=True, capture_output=True).stdout.decode().strip()
244 if curr_branch != SECBRANCH:
245 error("Please checkout UCT to '" + SECBRANCH + "' branch")
246 error("currently it is on: " + curr_branch)
247 exit(-1)
248
249 cve_list = sanitize_cves(arg_cve, arg_cve_list)
250
251 # Print prompt
252 info("Merging:")
253 # print first 5 CVEs
254 for cve_num in cve_list[:5]:
255 print(" " + cve_num)
256 if len(cve_list) - 5 > 0:
257 print(" ...and " + str(len(cve_list) - 5) + " more")
258 if arg_kernel is None:
259 info("With all kernels")
260 else:
261 info("with:")
262 for kernel in arg_kernel:
263 print(" " + kernel)
264 if arg_from is not None:
265 info("which from-status matches: " + arg_from)
266 if arg_to is not None:
267 info("which to-status matches: " + arg_to)
268
269 cve_path_list = build_path_list(cve_list)
270 to_status = build_status_dict(cve_path_list)
271
272 # Apply changes
273 for cve_path in cve_path_list:
274 with fileinput.input(files=cve_path, inplace=True) as f:
275 for line in f:
276 if status_line_re.search(line):
277 kernel = line.split(sep=':')[0]
278 status = line.split(sep=' ')[1]
279
280 if (arg_kernel is None or kernel in arg_kernel) and kernel in to_status[cve_path].keys():
281 if arg_from is None or from_re.search(status):
282 if arg_to is None or to_re.search(to_status[cve_path][kernel]):
283 print(kernel + ": " + to_status[cve_path][kernel], end='\n')
284 continue
285 print(line, end='')
286
287 # Prepare for git push
288 git_status = subprocess.run(["git", "status", "--short"], capture_output=True).stdout.decode()
289 if len(git_status) == 0:
290 warn('No changes being made')
291 else:
292 for cve_num in cve_path_list:
293 try:
294 cve_lib.git_add(cve_num)
295 except ValueError as e:
296 error(e)
297 try:
298 if len(cve_path_list) == 1:
299 cve_lib.git_commit("kernel/" + cve_path_list[0].split(sep="/")[-1] + ": autotriage")
300 else:
301 cve_lib.git_commit("kernel: autotriage")
302 except ValueError as e:
303 error(e.args[0])
304
305
306if __name__ == '__main__':
307 sys.exit(main())

Subscribers

People subscribed via source and target branches