alpm_lint/lint_rules/source_info/unsafe_checksum.rs
1//! Ensures that each [alpm-package-source-checksum] in [SRCINFO] data uses a safe hash function.
2//!
3//! [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
4//! [alpm-package-source-checksum]: https://alpm.archlinux.page/specifications/alpm-package-source-checksum.7.html
5
6use alpm_types::{ChecksumAlgorithm, SystemArchitecture};
7use documented::Documented;
8use strum::VariantArray;
9
10use crate::{
11 internal_prelude::*,
12 issue::SourceInfoIssue,
13 lint_rules::source_info::source_info_from_resource,
14};
15
16/// # What it does
17///
18/// Ensures that each [alpm-package-source-checksum] in [SRCINFO] data uses a safe hash function.
19///
20/// # Why is this bad?
21///
22/// Upstream artifacts are validated against hash digests (see [alpm-package-source-checksum]) set
23/// in [PKGBUILD] and [SRCINFO] files.
24///
25/// Some [hash functions] (e.g. [MD-5] and [SHA-1]) used for creating these hash digests are unsafe
26/// to use from a cryptographic perspective. These algorithms should be avoided to prevent hash
27/// collisions and potential abuse.
28///
29/// Using unsafe hash algorithms allows attackers to craft malicious artifacts that pass the
30/// checksum check. Further, attackers could swap existing artifacts with these malicious artifacts
31/// and compromise a package on (re)build.
32///
33/// # Example
34///
35/// ```ini,ignore
36/// pkgbase = test
37/// pkgver = 1.0.0
38/// pkgrel = 1
39/// arch = x86_64
40/// source = https://domain.tld/testing/x86_64_test.tar.gz
41/// md5sums = 10245815f893d79f3d779690774f0b43
42/// ```
43///
44/// Use instead:
45///
46/// ```ini,ignore
47/// pkgbase = test
48/// pkgver = 1.0.0
49/// pkgrel = 1
50/// arch = x86_64
51/// source = https://domain.tld/testing/x86_64_test.tar.gz
52/// sha512sums = 1816c57b4abf31eb7c57a66bfb0f0ee5cef9398b5e4cc303468e08dae2702da55978402da94673e444f8c02754e94dedef4d12450319383c3a481d1c5cd90c82
53/// ```
54///
55/// [MD-5]: https://en.wikipedia.org/wiki/MD-5
56/// [PKGBUILD]: https://man.archlinux.org/man/PKGBUILD.5
57/// [RFC 0046]: https://rfc.archlinux.page/0046-upstream-package-sources/
58/// [SHA-1]: https://en.wikipedia.org/wiki/SHA-1
59/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
60/// [alpm-package-source-checksum]: https://alpm.archlinux.page/specifications/alpm-package-source-checksum.7.html
61/// [hash functions]: https://en.wikipedia.org/wiki/Hash_function
62#[derive(Clone, Debug, Documented)]
63pub struct UnsafeChecksum {}
64
65impl UnsafeChecksum {
66 /// Create a new, boxed instance of [`UnsafeChecksum`].
67 pub fn new_boxed(_: &LintRuleConfiguration) -> Box<dyn LintRule> {
68 Box::new(Self {})
69 }
70
71 /// Helper function to create a lint issue for unsafe checksum field.
72 fn create_checksum_issue(
73 &self,
74 field_name: &str,
75 value: &str,
76 architecture: Option<SystemArchitecture>,
77 ) -> LintIssue {
78 LintIssue::from_rule(
79 self,
80 SourceInfoIssue::BaseField {
81 field_name: field_name.to_string(),
82 value: value.to_string(),
83 context: "Unsafe algorithm".to_string(),
84 architecture,
85 }
86 .into(),
87 )
88 }
89}
90
91impl LintRule for UnsafeChecksum {
92 fn name(&self) -> &'static str {
93 "unsafe_checksum"
94 }
95
96 fn scope(&self) -> LintScope {
97 LintScope::SourceInfo
98 }
99
100 fn level(&self) -> Level {
101 crate::Level::Deny
102 }
103
104 fn documentation(&self) -> String {
105 UnsafeChecksum::DOCS.into()
106 }
107
108 fn help_text(&self) -> String {
109 format!(
110 r#"Some hash algorithms are deprecated: {}.
111Using deprecated checksum algorithms for the verification of artifacts is a security risk.
112
113Instead, use one of the following algorithms: {}
114"#,
115 ChecksumAlgorithm::VARIANTS
116 .iter()
117 .filter(|algo| algo.is_deprecated())
118 .map(|var| var.to_string())
119 .collect::<Vec<String>>()
120 .join(", "),
121 ChecksumAlgorithm::VARIANTS
122 .iter()
123 .filter(|algo| !algo.is_deprecated())
124 .map(|var| var.to_string())
125 .collect::<Vec<String>>()
126 .join(", ")
127 )
128 }
129
130 fn run(&self, resources: &Resources, issues: &mut Vec<LintIssue>) -> Result<(), Error> {
131 // Extract the SourceInfo from the given resources.
132 let source_info = source_info_from_resource(resources, self.scoped_name())?;
133 let base = &source_info.base;
134
135 // Check for SHA1 checksums - these are unsafe
136 for (_source, checksum) in base.sources.iter().zip(base.sha1_checksums.iter()) {
137 if !checksum.is_skipped() {
138 issues.push(self.create_checksum_issue("sha1sums", &checksum.to_string(), None));
139 }
140 }
141
142 // Check for MD5 checksums - these are unsafe
143 for (_source, checksum) in base.sources.iter().zip(base.md5_checksums.iter()) {
144 if !checksum.is_skipped() {
145 issues.push(self.create_checksum_issue("md5sums", &checksum.to_string(), None));
146 }
147 }
148
149 // Also check architecture-specific checksums
150 for (architecture, arch_props) in &base.architecture_properties {
151 // Check SHA1 checksums in architecture-specific properties
152 for (_source, checksum) in arch_props
153 .sources
154 .iter()
155 .zip(arch_props.sha1_checksums.iter())
156 {
157 if !checksum.is_skipped() {
158 issues.push(self.create_checksum_issue(
159 "sha1sums",
160 &checksum.to_string(),
161 Some(architecture.clone()),
162 ));
163 }
164 }
165
166 // Check MD5 checksums in architecture-specific properties
167 for (_source, checksum) in arch_props
168 .sources
169 .iter()
170 .zip(arch_props.md5_checksums.iter())
171 {
172 if !checksum.is_skipped() {
173 issues.push(self.create_checksum_issue(
174 "md5sums",
175 &checksum.to_string(),
176 Some(architecture.clone()),
177 ));
178 }
179 }
180 }
181
182 Ok(())
183 }
184}