xensec_internal_tools/
xsagit.rs1use crate::prelude::*;
6
7pub static MAIN_BRANCH: &str = "master";
8pub static MAIN_REMOTE: &str = "origin";
9
10pub type XsaNum = u32;
11pub type XsaVsn = u32;
12
13pub struct Repo {
14 g2: git2::Repository,
15 cfg: Arc<Config>,
16}
17
18pub type Tagged = BTreeMap<XsaNum, XsaVsn>;
19
20#[derive(Debug,Clone,Serialize,Deserialize)]
21pub struct Parsed {
22 pub cves: Vec<CveName>,
23 pub public: bool,
24}
25
26impl Repo {
27 #[throws(AE)]
28 pub fn new(cfg: &Arc<Config>, cache: &mut cache::Cache) -> Self {
29 let g2 = git2::Repository::open(&cfg.xsa_git)
30 .context("open xsa.git repo")?;
31
32 let lockfile = format!("{}/.git/xensec.lock", &cfg.xsa_git);
33 (||{
34 let lock = File::create(&lockfile).context("open/create")?;
35 lock.try_lock_exclusive().context("lock")?;
36 Box::leak(Box::new(lock));
37 Ok::<_,AE>(())
38 })()
39 .with_context(|| lockfile.clone())
40 .context("acquire xensec tools mutual exclusion lock")?;
41
42 let mut repo = Repo { g2, cfg: cfg.clone() };
43
44 let did1 = cache.run_git_fetch_pull(
45 RefreshKind::XsaGit, &mut repo,
46 &["fetch",
47 "--prune"],
48 MAIN_REMOTE,
49 &[MAIN_BRANCH],
50 &[&{ let t="refs/tags/*"; format!("{}:{}",t,t) }],
51 )
52 .context("fetch xsa.git")?;
53
54 let did2 = cache.run_git_fetch_pull(
55 RefreshKind::XsaGit, &mut repo,
56 &["merge",
57 "--ff-only"],
58 &format!("refs/remotes/{}/{}", MAIN_REMOTE, MAIN_BRANCH),
59 &[],
60 &[],
61 )
62 .context("update xsa.git")?;
63
64 assert_eq!(did1,did2);
65 cache.did_refresh(did1);
66
67 repo
68 }
69
70 #[throws(AE)]
71 pub fn head_commitid(&self) -> String {
72 self.g2().refname_to_id("HEAD")
73 .context("look up HEAD")?
74 .to_string()
75 }
76
77 #[throws(AE)]
78 pub fn list_by_tags(&self) -> Tagged {
79 let tags = self.g2
80 .tag_names(Some("xsa-*")).context("list tagw")?;
81 let mut xsas = tags.into_iter()
82 .filter_map(|maybe_tagname| {
83 let mut pieces = maybe_tagname?.splitn(3,'-');
84 assert_eq!( pieces.next(), Some("xsa"));
85 let xsa = pieces.next()?.parse().ok()?;
86 let vsn = pieces.next()?.strip_prefix("v")?.parse().ok()?;
87 Some((xsa,vsn))
88 }).collect_vec();
89 xsas.sort();
90 xsas.into_iter().group_by(|t| t.0)
91 .into_iter().map(|(_,vsns)| vsns.max().unwrap())
92 .collect()
93 }
94
95 #[throws(AE)]
96 pub fn parse_advisory(&self, xsa: XsaNum, vsn: XsaVsn) -> Parsed {
97 (||{
98 let std::process::Output { status, stdout, stderr:_ } = Command::new
99 ("Infra/parse-advisory")
100 .args(&[
101 format!("-v{}", vsn),
102 xsa.to_string(),
103 ])
104 .current_dir(&self.cfg.xsa_git)
105 .output().context("spawn")?;
106
107 if ! status.success() {
108 throw!(anyhow!("failed, wait status {:?}", status));
109 }
110 let json = serde_json::from_slice(&stdout)
111 .context("parse as JSON")?;
112
113 #[derive(Debug,Deserialize)]
114 #[allow(non_snake_case)]
115 struct Extract {
116 CVE: Option<String>,
117 EmbargoTime: Option<String>,
118 Withdrawn: Option<u8>,
119 }
120
121 let extract: Extract = serde_json::from_value(json)
122 .context("interpret JSON")?;
123
124 let cves: Vec<CveName> =
125 extract.CVE
126 .unwrap_or_default()
127 .split(',')
128 .map(|s| s.parse())
129 .collect::<Result<Vec<_>,_>>()
130 .context("parse CVE list")?;
131
132 let public = extract.EmbargoTime.is_none();
133 let _withdrawn = extract.Withdrawn.unwrap_or_default() != 0;
134 Ok::<_,AE>(Parsed { cves, public })
137 })().with_context(|| format!("parse XSA-{} v{}", xsa, vsn))?
138 }
139
140 #[throws(AE)]
141 pub fn generate_cve_json(&self, xsa: XsaNum, xstate: &analysis::XsaState)
143 -> BTreeMap<CveName, String >
144 {
145 (||{
146 let jpaths = xstate.cves.keys().map(
147 |cve| (cve.clone(), format!("{}/{}.json", &self.cfg.xsa_git, cve))
148 ).collect::<BTreeMap<_,_>>();
149
150 for f in jpaths.values() {
151 match fs::remove_file(f) {
152 Err(e) if e.kind() == ErrorKind::NotFound => { },
153 x => x.with_context(|| f.to_owned()).context("remove, prep")?,
154 }
155 }
156
157 let st = Command::new("Infra/parse-advisory")
158 .args(&[
159 "-CC",
160 &format!("-v{}", xstate.vsn),
161 &xsa.to_string(),
162 ])
163 .current_dir(&self.cfg.xsa_git)
164 .stdout(std::process::Stdio::null())
165 .status().context("run parse-advisory")?;
166 if ! st.success() {
167 throw!(anyhow!("parse-advisory -CC failed: {:?}", st))
168 }
169
170 for f in jpaths.values() {
171 fs::metadata(f).with_context(|| f.to_owned()).context("check")?;
172 }
173
174 Ok::<_,AE>(jpaths)
175 })()
176 .with_context(|| format!("generate CVE JSON for XSA-{}-v{}",
177 xsa, xstate.vsn))?
178 }
179
180}
181
182impl githelp::Repo for Repo {
183 fn g2(&self) -> &git2::Repository { &self.g2 }
184 fn cfg(&self) -> &Config { &self.cfg }
185 fn dir(&self) -> &str { &self.cfg.xsa_git }
186 fn def_refname(&self) -> &str { "HEAD" }
187}