GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/acl.cc
Date: 2025-07-13 02:35:07
Exec Total Coverage
Lines: 138 151 91.4%
Branches: 104 132 78.8%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "acl.h"
6
7 #include <string.h>
8
9 #include <algorithm>
10 #include <cassert>
11 #include <cstring>
12 #include <vector>
13
14 #include "util/posix.h"
15
16 using namespace std; // NOLINT
17
18 #ifdef COMPARE_TO_LIBACL
19 #include "acl/libacl.h"
20 #else // COMPARE_TO_LIBACL
21
22 // ACL permission bits
23 #define ACL_READ (0x04)
24 #define ACL_WRITE (0x02)
25 #define ACL_EXECUTE (0x01)
26
27 // ACL tag types
28 #define ACL_UNDEFINED_TAG (0x00)
29 #define ACL_USER_OBJ (0x01)
30 #define ACL_USER (0x02)
31 #define ACL_GROUP_OBJ (0x04)
32 #define ACL_GROUP (0x08)
33 #define ACL_MASK (0x10)
34 #define ACL_OTHER (0x20)
35
36 // ACL qualifier constants
37 #define ACL_UNDEFINED_ID ((id_t) - 1)
38
39 #endif // COMPARE_TO_LIBACL
40
41 #define ACL_EA_VERSION 0x0002
42
43 // ACL data structures
44 struct acl_ea_entry {
45 u_int16_t e_tag;
46 u_int16_t e_perm;
47 u_int32_t e_id;
48
49 // implements sorting compatible with libacl
50 1209 bool operator<(const acl_ea_entry &other) const {
51
2/2
✓ Branch 0 taken 1131 times.
✓ Branch 1 taken 78 times.
1209 if (e_tag != other.e_tag) {
52 1131 return e_tag < other.e_tag;
53 }
54 78 return e_id < other.e_id;
55 }
56 };
57
58 struct acl_ea_header {
59 u_int32_t a_version;
60 acl_ea_entry a_entries[0];
61 };
62
63 286 static int acl_from_text_to_string_entries(const string &acl_string,
64 vector<string> &string_entries) {
65 286 std::size_t entry_pos = 0;
66
2/2
✓ Branch 0 taken 1131 times.
✓ Branch 1 taken 182 times.
1313 while (entry_pos != string::npos) {
67 size_t entry_length;
68 size_t next_pos;
69 1131 size_t const sep_pos = acl_string.find_first_of(",\n", entry_pos);
70
2/2
✓ Branch 0 taken 286 times.
✓ Branch 1 taken 845 times.
1131 if (sep_pos == string::npos) {
71
2/2
✓ Branch 1 taken 182 times.
✓ Branch 2 taken 104 times.
286 if (acl_string.length() > entry_pos) {
72 182 entry_length = acl_string.length() - entry_pos;
73 182 next_pos = string::npos;
74 } else {
75 // we've just looked past a trailing delimiter
76 104 break;
77 }
78 } else {
79
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 845 times.
845 assert(sep_pos >= entry_pos);
80 845 entry_length = sep_pos - entry_pos;
81 845 next_pos = sep_pos + 1;
82 }
83
2/2
✓ Branch 0 taken 143 times.
✓ Branch 1 taken 884 times.
1027 if (entry_length == 0) {
84 // libacl tolerates excessive whitespace but not excessive delimiters.
85 // It's simpler for us to treat whitespace as delimiters.
86 143 entry_pos = next_pos;
87 143 continue;
88 }
89
1/2
✓ Branch 1 taken 884 times.
✗ Branch 2 not taken.
884 string entry(acl_string, entry_pos, entry_length);
90 884 entry_pos = next_pos;
91
92 // search for '#'-starting comment, discard if found
93 884 size_t const comment_pos = entry.find('#');
94
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 884 times.
884 if (comment_pos != string::npos) {
95 entry = string(entry, 0, comment_pos);
96 }
97
98 // TODO(autkin): trim whitespace on both ends
99
100 // discard empty lines
101
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 884 times.
884 if (entry.length() == 0) {
102 continue;
103 }
104
105
1/2
✓ Branch 1 taken 884 times.
✗ Branch 2 not taken.
884 string_entries.push_back(entry);
106
1/2
✓ Branch 1 taken 884 times.
✗ Branch 2 not taken.
884 }
107 286 return 0;
108 }
109
110 806 static int acl_parms_from_text(const string &str, u_int16_t *perms) {
111 // Currently unsupported syntax features found in setfacl:
112 // - X (capital x)
113 // - numeric syntax
114 // See "man 1 setfacl", "The perms field is..."
115
116 806 *perms = 0;
117
2/2
✓ Branch 5 taken 1456 times.
✓ Branch 6 taken 806 times.
2262 for (const char &c : str) {
118
4/5
✓ Branch 0 taken 533 times.
✓ Branch 1 taken 325 times.
✓ Branch 2 taken 156 times.
✓ Branch 3 taken 442 times.
✗ Branch 4 not taken.
1456 switch (c) {
119 533 case 'r':
120 533 *perms |= ACL_READ;
121 533 break;
122 325 case 'w':
123 325 *perms |= ACL_WRITE;
124 325 break;
125 156 case 'x':
126 156 *perms |= ACL_EXECUTE;
127 156 break;
128 442 case '-':
129 442 break;
130 default:
131 return EINVAL;
132 }
133 }
134 806 return 0;
135 }
136
137 819 static int acl_entry_from_text(const string &str, acl_ea_entry &entry) {
138 // break down to 3 fields by ':'
139 // type:qualifier:permissions according to terminology
140 // e_tag:e_id:e_perm are acl_ea_entry field names
141 819 size_t sep_pos = str.find(':');
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 819 times.
819 if (sep_pos == string::npos) {
143 return EINVAL;
144 }
145
1/2
✓ Branch 1 taken 819 times.
✗ Branch 2 not taken.
819 string const type(str, 0, sep_pos);
146 819 size_t next_field_pos = sep_pos + 1;
147 819 sep_pos = str.find(':', next_field_pos);
148
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 819 times.
819 if (sep_pos == string::npos) {
149 return EINVAL;
150 }
151
1/2
✓ Branch 1 taken 819 times.
✗ Branch 2 not taken.
819 string const qualifier(str, next_field_pos, sep_pos - next_field_pos);
152 819 next_field_pos = sep_pos + 1;
153
1/2
✓ Branch 2 taken 819 times.
✗ Branch 3 not taken.
819 string const permissions(str, next_field_pos);
154
155
6/6
✓ Branch 1 taken 806 times.
✓ Branch 2 taken 13 times.
✓ Branch 4 taken 312 times.
✓ Branch 5 taken 494 times.
✓ Branch 6 taken 325 times.
✓ Branch 7 taken 494 times.
819 if (!type.compare("user") || !type.compare("u")) {
156
2/2
✓ Branch 1 taken 195 times.
✓ Branch 2 taken 130 times.
325 entry.e_tag = qualifier.empty() ? ACL_USER_OBJ : ACL_USER;
157
6/6
✓ Branch 1 taken 455 times.
✓ Branch 2 taken 39 times.
✓ Branch 4 taken 195 times.
✓ Branch 5 taken 260 times.
✓ Branch 6 taken 234 times.
✓ Branch 7 taken 260 times.
494 } else if (!type.compare("group") || !type.compare("g")) {
158
2/2
✓ Branch 1 taken 182 times.
✓ Branch 2 taken 52 times.
234 entry.e_tag = qualifier.empty() ? ACL_GROUP_OBJ : ACL_GROUP;
159
6/6
✓ Branch 1 taken 247 times.
✓ Branch 2 taken 13 times.
✓ Branch 4 taken 169 times.
✓ Branch 5 taken 78 times.
✓ Branch 6 taken 182 times.
✓ Branch 7 taken 78 times.
260 } else if (!type.compare("other") || !type.compare("o")) {
160 182 entry.e_tag = ACL_OTHER;
161
6/6
✓ Branch 1 taken 39 times.
✓ Branch 2 taken 39 times.
✓ Branch 4 taken 26 times.
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 65 times.
✓ Branch 7 taken 13 times.
78 } else if (!type.compare("mask") || !type.compare("m")) {
162 65 entry.e_tag = ACL_MASK;
163 } else {
164 13 return EINVAL;
165 }
166 806 entry.e_tag = htole16(entry.e_tag);
167
168
2/2
✓ Branch 1 taken 624 times.
✓ Branch 2 taken 182 times.
806 if (qualifier.empty()) {
169 624 entry.e_id = ACL_UNDEFINED_ID;
170 } else {
171 char *at_null_terminator_if_number;
172 182 long number = strtol(qualifier.c_str(), &at_null_terminator_if_number, 10);
173
2/2
✓ Branch 0 taken 91 times.
✓ Branch 1 taken 91 times.
182 if (*at_null_terminator_if_number != '\0') {
174 bool ok;
175
2/2
✓ Branch 1 taken 65 times.
✓ Branch 2 taken 26 times.
91 if (entry.e_tag == htole16(ACL_USER)) {
176 [[maybe_unused]] gid_t main_gid;
177 uid_t uid;
178
1/2
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
65 ok = GetUidOf(qualifier, &uid, &main_gid);
179 65 number = uid;
180
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 } else if (entry.e_tag == htole16(ACL_GROUP)) {
181 gid_t gid;
182
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 ok = GetGidOf(qualifier, &gid);
183 26 number = gid;
184 } else {
185 assert(false);
186 }
187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
91 if (!ok) {
188 return EINVAL;
189 }
190 }
191 182 entry.e_id = htole32(number);
192 }
193
194 // parse perms
195 u_int16_t host_byteorder_perms;
196 int ret;
197 806 ret = acl_parms_from_text(permissions, &host_byteorder_perms);
198
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 806 times.
806 if (ret) {
199 return ret;
200 }
201 806 entry.e_perm = htole16(host_byteorder_perms);
202
203 806 return 0;
204 819 }
205
206 273 static bool acl_valid_builtin(const vector<acl_ea_entry> &entries) {
207 // From man acl_valid:
208 // The three required entries ACL_USER_OBJ, ACL_GROUP_OBJ, and ACL_OTHER must
209 // exist exactly once in the ACL.
210 //
211 // If the ACL contains any ACL_USER or ACL_GROUP entries, then an ACL_MASK
212 // entry is also required.
213 //
214 // The ACL may contain at most one ACL_MASK entry.
215 //
216 // The user identifiers must be unique among all entries of type ACL_USER.
217 // The group identifiers must be unique among all entries of type ACL_GROUP.
218
219 273 bool types_met[ACL_OTHER + 1] = {
220 false,
221 };
222
223
2/2
✓ Branch 4 taken 754 times.
✓ Branch 5 taken 260 times.
1014 for (auto entry_it = entries.begin(); entry_it != entries.end(); ++entry_it) {
224 754 const acl_ea_entry &e = *entry_it;
225
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 754 times.
754 assert(e.e_tag <= ACL_OTHER);
226 754 bool &type_met = types_met[e.e_tag];
227
2/3
✓ Branch 0 taken 598 times.
✓ Branch 1 taken 156 times.
✗ Branch 2 not taken.
754 switch (e.e_tag) {
228 // at most one of these types
229 598 case ACL_USER_OBJ:
230 case ACL_GROUP_OBJ:
231 case ACL_OTHER:
232 case ACL_MASK:
233
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 585 times.
598 if (type_met) {
234 13 return false;
235 } else {
236 585 type_met = true;
237 }
238 585 break;
239 156 case ACL_USER:
240 case ACL_GROUP:
241 156 type_met = true;
242 156 break;
243 default:
244 assert(false);
245 }
246 }
247
4/4
✓ Branch 0 taken 169 times.
✓ Branch 1 taken 91 times.
✓ Branch 2 taken 156 times.
✓ Branch 3 taken 13 times.
260 if (!(types_met[ACL_USER_OBJ] && types_met[ACL_GROUP_OBJ]
248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 156 times.
156 && types_met[ACL_OTHER])) {
249 104 return false;
250 }
251
6/6
✓ Branch 0 taken 91 times.
✓ Branch 1 taken 65 times.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 78 times.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 65 times.
156 if ((types_met[ACL_USER] || types_met[ACL_GROUP]) && !types_met[ACL_MASK]) {
252 13 return false;
253 }
254 // TODO(autkin): ACL_USER, ACL_GROUP uniqueness checks. Not a pressing issue.
255 143 return true;
256 }
257
258 286 int acl_from_text_to_xattr_value(const string &textual_acl, char *&o_binary_acl,
259 size_t &o_size, bool &o_equiv_mode) {
260 int ret;
261
262 286 o_equiv_mode = true;
263
264 // get individual textual entries from one big text
265 286 vector<string> string_entries;
266
1/2
✓ Branch 1 taken 286 times.
✗ Branch 2 not taken.
286 ret = acl_from_text_to_string_entries(textual_acl, string_entries);
267
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 286 times.
286 if (ret) {
268 return ret;
269 }
270
271 // get individual entries in structural form
272 286 vector<acl_ea_entry> entries;
273 286 for (auto string_it = string_entries.begin();
274
2/2
✓ Branch 2 taken 819 times.
✓ Branch 3 taken 273 times.
1092 string_it != string_entries.end();
275 806 ++string_it) {
276 acl_ea_entry entry;
277
1/2
✓ Branch 2 taken 819 times.
✗ Branch 3 not taken.
819 ret = acl_entry_from_text(*string_it, entry);
278
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 806 times.
819 if (ret) {
279 13 return ret;
280 }
281
4/4
✓ Branch 0 taken 754 times.
✓ Branch 1 taken 52 times.
✓ Branch 2 taken 130 times.
✓ Branch 3 taken 624 times.
806 if (entry.e_tag == ACL_GROUP || entry.e_tag == ACL_USER) {
282 182 o_equiv_mode = false;
283 }
284
1/2
✓ Branch 1 taken 806 times.
✗ Branch 2 not taken.
806 entries.push_back(entry);
285 }
286
287 // sort entries as libacl does, to be able to use it in testing as a reference
288
1/2
✓ Branch 3 taken 273 times.
✗ Branch 4 not taken.
273 sort(entries.begin(), entries.end());
289
290 // reject what acl_valid() rejects, to be able to use it in testing
291
2/2
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 143 times.
273 if (!acl_valid_builtin(entries)) {
292 130 return EINVAL;
293 }
294
295 // if nothing but usual u,g,o bits, don't produce a binary. Mimicking libacl.
296
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 65 times.
143 if (o_equiv_mode) {
297 78 o_binary_acl = NULL;
298 78 o_size = 0;
299 78 return 0;
300 }
301
302 // get one big buffer with all the entries in the "on-disk" xattr format
303 65 size_t const acl_entry_count = entries.size();
304 65 size_t const buf_size = sizeof(acl_ea_header)
305 65 + (acl_entry_count * sizeof(acl_ea_entry));
306 65 char *buf = static_cast<char *>(malloc(buf_size));
307
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (!buf) {
308 return ENOMEM;
309 }
310 65 acl_ea_header *header = reinterpret_cast<acl_ea_header *>(buf);
311 65 header->a_version = htole32(ACL_EA_VERSION);
312 65 acl_ea_entry *ext_entry = reinterpret_cast<acl_ea_entry *>(header + 1);
313
2/2
✓ Branch 3 taken 377 times.
✓ Branch 4 taken 65 times.
442 for (auto entry_it = entries.begin(); entry_it != entries.end(); ++entry_it) {
314 377 *ext_entry = *entry_it;
315 377 ext_entry += 1;
316 }
317
318 65 o_binary_acl = buf;
319 65 o_size = buf_size;
320 65 return 0;
321 286 }
322
323 #ifdef COMPARE_TO_LIBACL
324 int acl_from_text_to_xattr_value_libacl(const string textual_acl,
325 char *&o_binary_acl, size_t &o_size,
326 bool &o_equiv_mode) {
327 acl_t acl = acl_from_text(
328 textual_acl.c_str()); // Convert ACL string to acl_t object
329 if (!acl) {
330 return EINVAL;
331 }
332 if (acl_valid(acl) != 0) {
333 acl_free(acl);
334 return EINVAL;
335 }
336
337 // check if the ACL string contains more than the synthetic ACLs
338 int equiv = acl_equiv_mode(acl, NULL);
339 assert(equiv != -1);
340
341 o_equiv_mode = equiv == 0;
342 if (!o_equiv_mode) {
343 o_binary_acl = (char *)acl_to_xattr(acl, &o_size);
344 } else {
345 o_binary_acl = NULL;
346 o_size = 0;
347 }
348 acl_free(acl);
349 return 0;
350 }
351
352 int acl_from_text_to_xattr_value_both_impl(const string textual_acl,
353 char *&o_binary_acl, size_t &o_size,
354 bool &o_equiv_mode) {
355 struct impl_result {
356 int ret;
357 size_t binary_size;
358 char *binary_acl;
359 bool equiv_mode;
360 } b, l;
361 b.ret = acl_from_text_to_xattr_value(textual_acl, b.binary_acl, b.binary_size,
362 b.equiv_mode);
363 l.ret = acl_from_text_to_xattr_value_libacl(textual_acl, l.binary_acl,
364 l.binary_size, l.equiv_mode);
365 assert(b.ret == l.ret);
366 if (!l.ret) {
367 assert(b.binary_size == l.binary_size);
368 assert(0 == memcmp(b.binary_acl, l.binary_acl, b.binary_size));
369 assert(b.equiv_mode == l.equiv_mode);
370 free(l.binary_acl);
371 }
372 o_binary_acl = b.binary_acl;
373 o_size = b.binary_size;
374 o_equiv_mode = b.equiv_mode;
375 return b.ret;
376 }
377 #endif // COMPARE_TO_LIBACL
378