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}