GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/manifest_fetch.cc
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 84 109 77.1%
Branches: 64 117 54.7%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "manifest_fetch.h"
6
7 #include <cassert>
8 #include <cstring>
9 #include <string>
10 #include <vector>
11
12 #include "crypto/hash.h"
13 #include "crypto/signature.h"
14 #include "manifest.h"
15 #include "network/download.h"
16 #include "util/smalloc.h"
17 #include "whitelist.h"
18
19 using namespace std; // NOLINT
20
21 namespace manifest {
22
23 /**
24 * Verifies the manifest, the certificate, and the whitelist.
25 * If base_url is empty, uses the probe_hosts feature from download manager.
26 *
27 * @note Ownership of manifest_data is transferred to the ensemble.
28 */
29 1128 static Failures DoVerify(unsigned char *manifest_data, size_t manifest_size,
30 const std::string &base_url,
31 const std::string &repository_name,
32 const uint64_t minimum_timestamp,
33 const shash::Any *base_catalog,
34 signature::SignatureManager *signature_manager,
35 download::DownloadManager *download_manager,
36 ManifestEnsemble *ensemble) {
37
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1128 times.
1128 assert(ensemble);
38 1128 const bool probe_hosts = base_url == "";
39 1128 Failures result = kFailUnknown;
40 bool retval_b;
41 download::Failures retval_dl;
42 whitelist::Failures retval_wl;
43 whitelist::Whitelist whitelist(repository_name, download_manager,
44
1/2
✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
1128 signature_manager);
45
1/2
✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
1128 string certificate_url = base_url + "/"; // rest is in manifest
46
1/2
✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
1128 shash::Any certificate_hash;
47 1128 cvmfs::MemSink certificate_memsink;
48 download::JobInfo download_certificate(&certificate_url, true, probe_hosts,
49 &certificate_hash,
50
1/2
✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
1128 &certificate_memsink);
51
52 // Load Manifest
53 1128 ensemble->raw_manifest_buf = manifest_data;
54 1128 ensemble->raw_manifest_size = manifest_size;
55
1/2
✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
1128 ensemble->manifest = manifest::Manifest::LoadMem(ensemble->raw_manifest_buf,
56 ensemble->raw_manifest_size);
57
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1128 times.
1128 if (!ensemble->manifest)
58 return kFailIncomplete;
59
60 // Basic manifest sanity check
61
3/6
✓ Branch 1 taken 1128 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 49 times.
✓ Branch 6 taken 1079 times.
1128 if (ensemble->manifest->repository_name() != repository_name) {
62
1/5
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
98 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
63 "repository name does not match (found %s, expected %s)",
64
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
98 ensemble->manifest->repository_name().c_str(),
65 repository_name.c_str());
66 49 result = kFailNameMismatch;
67 49 goto cleanup;
68 }
69
4/10
✓ Branch 2 taken 1079 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 1079 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 1079 times.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 1079 times.
1079 if (ensemble->manifest->root_path() != shash::Md5(shash::AsciiPtr(""))) {
70 result = kFailRootMismatch;
71 goto cleanup;
72 }
73
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1079 times.
1079 if (ensemble->manifest->publish_timestamp() < minimum_timestamp) {
74 result = kFailOutdated;
75 goto cleanup;
76 }
77
78 // Quick way out: hash matches base catalog
79
6/6
✓ Branch 0 taken 540 times.
✓ Branch 1 taken 539 times.
✓ Branch 4 taken 99 times.
✓ Branch 5 taken 441 times.
✓ Branch 6 taken 99 times.
✓ Branch 7 taken 980 times.
1079 if (base_catalog && (ensemble->manifest->catalog_hash() == *base_catalog)) {
80 99 return kFailOk;
81 }
82
83 // Load certificate
84 980 certificate_hash = ensemble->manifest->certificate();
85
1/2
✓ Branch 1 taken 980 times.
✗ Branch 2 not taken.
980 ensemble->FetchCertificate(certificate_hash);
86
2/2
✓ Branch 0 taken 978 times.
✓ Branch 1 taken 2 times.
980 if (!ensemble->cert_buf) {
87
2/4
✓ Branch 1 taken 978 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 978 times.
✗ Branch 5 not taken.
978 certificate_url += ensemble->manifest->MakeCertificatePath();
88
1/2
✓ Branch 1 taken 978 times.
✗ Branch 2 not taken.
978 retval_dl = download_manager->Fetch(&download_certificate);
89
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 978 times.
978 if (retval_dl != download::kFailOk) {
90 result = kFailLoad;
91 goto cleanup;
92 }
93 978 ensemble->cert_buf = certificate_memsink.data();
94 978 ensemble->cert_size = certificate_memsink.pos();
95 978 certificate_memsink.Release();
96 }
97
1/2
✓ Branch 1 taken 980 times.
✗ Branch 2 not taken.
980 retval_b = signature_manager->LoadCertificateMem(ensemble->cert_buf,
98 ensemble->cert_size);
99
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 980 times.
980 if (!retval_b) {
100 result = kFailBadCertificate;
101 goto cleanup;
102 }
103
104 // Verify manifest
105 1960 retval_b = signature_manager->VerifyLetter(
106
1/2
✓ Branch 1 taken 980 times.
✗ Branch 2 not taken.
980 ensemble->raw_manifest_buf, ensemble->raw_manifest_size, false);
107
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 980 times.
980 if (!retval_b) {
108 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
109 "failed to verify repository manifest");
110 result = kFailBadSignature;
111 goto cleanup;
112 }
113
114 // Load whitelist and verify
115
1/2
✓ Branch 1 taken 980 times.
✗ Branch 2 not taken.
980 retval_wl = whitelist.LoadUrl(base_url);
116
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 980 times.
980 if (retval_wl != whitelist::kFailOk) {
117 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
118 "whitelist verification failed (%d): %s", retval_wl,
119 whitelist::Code2Ascii(retval_wl));
120 result = kFailBadWhitelist;
121 goto cleanup;
122 }
123
124
1/2
✓ Branch 1 taken 980 times.
✗ Branch 2 not taken.
980 retval_wl = whitelist.VerifyLoadedCertificate();
125
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 882 times.
980 if (retval_wl != whitelist::kFailOk) {
126
1/2
✓ Branch 2 taken 98 times.
✗ Branch 3 not taken.
98 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
127 "failed to verify repository signature against whitelist (%d): %s",
128 retval_wl, whitelist::Code2Ascii(retval_wl));
129 98 result = kFailInvalidCertificate;
130 98 goto cleanup;
131 }
132
133
1/2
✓ Branch 1 taken 882 times.
✗ Branch 2 not taken.
882 whitelist.CopyBuffers(&ensemble->whitelist_size, &ensemble->whitelist_buf,
134 &ensemble->whitelist_pkcs7_size,
135 &ensemble->whitelist_pkcs7_buf);
136
137 882 return kFailOk;
138
139 147 cleanup:
140
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 delete ensemble->manifest;
141 147 ensemble->manifest = NULL;
142
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 if (ensemble->raw_manifest_buf)
143 147 free(ensemble->raw_manifest_buf);
144
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 49 times.
147 if (ensemble->cert_buf)
145 98 free(ensemble->cert_buf);
146
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 147 times.
147 if (ensemble->whitelist_buf)
147 free(ensemble->whitelist_buf);
148
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 147 times.
147 if (ensemble->whitelist_pkcs7_buf)
149 free(ensemble->whitelist_pkcs7_buf);
150 147 ensemble->raw_manifest_buf = NULL;
151 147 ensemble->cert_buf = NULL;
152 147 ensemble->whitelist_buf = NULL;
153 147 ensemble->whitelist_pkcs7_buf = NULL;
154 147 ensemble->raw_manifest_size = 0;
155 147 ensemble->cert_size = 0;
156 147 ensemble->whitelist_size = 0;
157 147 ensemble->whitelist_pkcs7_size = 0;
158 147 return result;
159 1128 }
160
161 /**
162 * Downloads and verifies the manifest, the certificate, and the whitelist.
163 * If base_url is empty, uses the probe_hosts feature from download manager.
164 */
165 1272 static Failures DoFetch(const std::string &base_url,
166 const std::string &repository_name,
167 const uint64_t minimum_timestamp,
168 const shash::Any *base_catalog,
169 signature::SignatureManager *signature_manager,
170 download::DownloadManager *download_manager,
171 ManifestEnsemble *ensemble) {
172
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1272 times.
1272 assert(ensemble);
173 1272 const bool probe_hosts = base_url == "";
174 download::Failures retval_dl;
175
2/4
✓ Branch 2 taken 1272 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1272 times.
✗ Branch 6 not taken.
2544 const string manifest_url = base_url + string("/.cvmfspublished");
176 1272 cvmfs::MemSink manifest_memsink;
177 download::JobInfo download_manifest(&manifest_url, false, probe_hosts, NULL,
178
1/2
✓ Branch 1 taken 1272 times.
✗ Branch 2 not taken.
1272 &manifest_memsink);
179
180
1/2
✓ Branch 1 taken 1272 times.
✗ Branch 2 not taken.
1272 retval_dl = download_manager->Fetch(&download_manifest);
181
2/2
✓ Branch 0 taken 144 times.
✓ Branch 1 taken 1128 times.
1272 if (retval_dl != download::kFailOk) {
182
1/2
✓ Branch 2 taken 144 times.
✗ Branch 3 not taken.
144 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
183 "failed to download repository manifest (%d - %s)", retval_dl,
184 download::Code2Ascii(retval_dl));
185 144 return kFailLoad;
186 }
187
188 1128 manifest_memsink.Release();
189
1/2
✓ Branch 3 taken 1128 times.
✗ Branch 4 not taken.
1128 return DoVerify(manifest_memsink.data(), manifest_memsink.pos(), base_url,
190 repository_name, minimum_timestamp, base_catalog,
191 1128 signature_manager, download_manager, ensemble);
192 1272 }
193
194 /**
195 * If the whitelist or the manifest are corrupted, fail-over once to another
196 * stratum 1 if more than a single stratum 1 is known.
197 */
198 1272 Failures Fetch(const std::string &base_url, const std::string &repository_name,
199 const uint64_t minimum_timestamp, const shash::Any *base_catalog,
200 signature::SignatureManager *signature_manager,
201 download::DownloadManager *download_manager,
202 ManifestEnsemble *ensemble) {
203 1272 Failures result = DoFetch(base_url, repository_name, minimum_timestamp,
204 base_catalog, signature_manager, download_manager,
205 ensemble);
206
2/2
✓ Branch 0 taken 147 times.
✓ Branch 1 taken 144 times.
291 if ((result != kFailOk) && (result != kFailLoad)
207
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 98 times.
147 && (result != kFailInvalidCertificate)
208
4/6
✓ Branch 0 taken 291 times.
✓ Branch 1 taken 981 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1272 times.
1563 && (download_manager->num_hosts() > 1)) {
209 LogCvmfs(kLogCache, kLogDebug | kLogSyslogWarn,
210 "failed to fetch manifest (%d - %s), trying another stratum 1",
211 result, Code2Ascii(result));
212 download_manager->SwitchHost();
213 result = DoFetch(base_url, repository_name, minimum_timestamp, base_catalog,
214 signature_manager, download_manager, ensemble);
215 }
216 1272 return result;
217 }
218
219 /**
220 * Verifies the manifest, the certificate, and the whitelist.
221 * If base_url is empty, uses the probe_hosts feature from download manager.
222 * Creates a copy of the manifest to verify.
223 */
224 Failures Verify(unsigned char *manifest_data, size_t manifest_size,
225 const std::string &base_url, const std::string &repository_name,
226 const uint64_t minimum_timestamp,
227 const shash::Any *base_catalog,
228 signature::SignatureManager *signature_manager,
229 download::DownloadManager *download_manager,
230 ManifestEnsemble *ensemble) {
231 unsigned char *manifest_copy = reinterpret_cast<unsigned char *>(
232 smalloc(manifest_size));
233 memcpy(manifest_copy, manifest_data, manifest_size);
234 return DoVerify(manifest_copy, manifest_size, base_url, repository_name,
235 minimum_timestamp, base_catalog, signature_manager,
236 download_manager, ensemble);
237 }
238
239 } // namespace manifest
240