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], [SHA-1] and [CRC-32/CKSUM]) used for creating these hash
26/// digests are unsafe to use from a cryptographic perspective. These algorithms should be avoided
27/// to prevent hash 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/// [CRC-32/CKSUM]: https://en.wikipedia.org/wiki/Cyclic_redundancy_check
56/// [MD-5]: https://en.wikipedia.org/wiki/MD-5
57/// [PKGBUILD]: https://man.archlinux.org/man/PKGBUILD.5
58/// [RFC 0046]: https://rfc.archlinux.page/0046-upstream-package-sources/
59/// [SHA-1]: https://en.wikipedia.org/wiki/SHA-1
60/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
61/// [alpm-package-source-checksum]: https://alpm.archlinux.page/specifications/alpm-package-source-checksum.7.html
62/// [hash functions]: https://en.wikipedia.org/wiki/Hash_function
63#[derive(Clone, Debug, Documented)]
64pub struct UnsafeChecksum {}
65
66impl UnsafeChecksum {
67 /// Create a new, boxed instance of [`UnsafeChecksum`].
68 pub fn new_boxed(_: &LintRuleConfiguration) -> Box<dyn LintRule> {
69 Box::new(Self {})
70 }
71
72 /// Helper function to create a lint issue for unsafe checksum field.
73 fn create_checksum_issue(
74 &self,
75 field_name: &str,
76 value: &str,
77 architecture: Option<SystemArchitecture>,
78 ) -> LintIssue {
79 LintIssue::from_rule(
80 self,
81 SourceInfoIssue::BaseField {
82 field_name: field_name.to_string(),
83 value: value.to_string(),
84 context: "Unsafe algorithm".to_string(),
85 architecture,
86 }
87 .into(),
88 )
89 }
90}
91
92impl LintRule for UnsafeChecksum {
93 fn name(&self) -> &'static str {
94 "unsafe_checksum"
95 }
96
97 fn scope(&self) -> LintScope {
98 LintScope::SourceInfo
99 }
100
101 fn level(&self) -> Level {
102 crate::Level::Deny
103 }
104
105 fn documentation(&self) -> String {
106 UnsafeChecksum::DOCS.into()
107 }
108
109 fn help_text(&self) -> String {
110 format!(
111 r#"Some hash algorithms are deprecated: {}.
112Using deprecated checksum algorithms for the verification of artifacts is a security risk.
113
114Instead, use one of the following algorithms: {}
115"#,
116 ChecksumAlgorithm::VARIANTS
117 .iter()
118 .filter(|algo| algo.is_deprecated())
119 .map(|var| var.to_string())
120 .collect::<Vec<String>>()
121 .join(", "),
122 ChecksumAlgorithm::VARIANTS
123 .iter()
124 .filter(|algo| !algo.is_deprecated())
125 .map(|var| var.to_string())
126 .collect::<Vec<String>>()
127 .join(", ")
128 )
129 }
130
131 fn run(&self, resources: &Resources, issues: &mut Vec<LintIssue>) -> Result<(), Error> {
132 // Extract the SourceInfo from the given resources.
133 let source_info = source_info_from_resource(resources, self.scoped_name())?;
134 let base = &source_info.base;
135
136 // Check for SHA1 checksums - these are unsafe
137 for (_source, checksum) in base.sources.iter().zip(base.sha1_checksums.iter()) {
138 if !checksum.is_skipped() {
139 issues.push(self.create_checksum_issue("sha1sums", &checksum.to_string(), None));
140 }
141 }
142
143 // Check for MD5 checksums - these are unsafe
144 for (_source, checksum) in base.sources.iter().zip(base.md5_checksums.iter()) {
145 if !checksum.is_skipped() {
146 issues.push(self.create_checksum_issue("md5sums", &checksum.to_string(), None));
147 }
148 }
149
150 // Check for CRC checksums - these are unsafe
151 for (_source, checksum) in base.sources.iter().zip(base.crc_checksums.iter()) {
152 if !checksum.is_skipped() {
153 issues.push(self.create_checksum_issue("cksums", &checksum.to_string(), None));
154 }
155 }
156
157 // Also check architecture-specific checksums
158 for (architecture, arch_props) in &base.architecture_properties {
159 // Check SHA1 checksums in architecture-specific properties
160 for (_source, checksum) in arch_props
161 .sources
162 .iter()
163 .zip(arch_props.sha1_checksums.iter())
164 {
165 if !checksum.is_skipped() {
166 issues.push(self.create_checksum_issue(
167 "sha1sums",
168 &checksum.to_string(),
169 Some(architecture.clone()),
170 ));
171 }
172 }
173
174 // Check MD5 checksums in architecture-specific properties
175 for (_source, checksum) in arch_props
176 .sources
177 .iter()
178 .zip(arch_props.md5_checksums.iter())
179 {
180 if !checksum.is_skipped() {
181 issues.push(self.create_checksum_issue(
182 "md5sums",
183 &checksum.to_string(),
184 Some(architecture.clone()),
185 ));
186 }
187 }
188
189 // Check CRC checksums in architecture-specific properties
190 for (_source, checksum) in arch_props
191 .sources
192 .iter()
193 .zip(arch_props.crc_checksums.iter())
194 {
195 if !checksum.is_skipped() {
196 issues.push(self.create_checksum_issue(
197 "cksums",
198 &checksum.to_string(),
199 Some(architecture.clone()),
200 ));
201 }
202 }
203 }
204
205 Ok(())
206 }
207}