GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/manifest_fetch.cc
Date: 2024-04-28 02:33:07
Exec Total Coverage
Lines: 84 106 79.2%
Branches: 63 117 53.8%

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