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}