GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/acl.cc
Date: 2025-10-19 02:35:28
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 2883 bool operator<(const acl_ea_entry &other) const {
51
2/2
✓ Branch 0 taken 2697 times.
✓ Branch 1 taken 186 times.
2883 if (e_tag != other.e_tag) {
52 2697 return e_tag < other.e_tag;
53 }
54 186 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 682 static int acl_from_text_to_string_entries(const string &acl_string,
64 vector<string> &string_entries) {
65 682 std::size_t entry_pos = 0;
66
2/2
✓ Branch 0 taken 2697 times.
✓ Branch 1 taken 434 times.
3131 while (entry_pos != string::npos) {
67 size_t entry_length;
68 size_t next_pos;
69 2697 size_t const sep_pos = acl_string.find_first_of(",\n", entry_pos);
70
2/2
✓ Branch 0 taken 682 times.
✓ Branch 1 taken 2015 times.
2697 if (sep_pos == string::npos) {
71
2/2
✓ Branch 1 taken 434 times.
✓ Branch 2 taken 248 times.
682 if (acl_string.length() > entry_pos) {
72 434 entry_length = acl_string.length() - entry_pos;
73 434 next_pos = string::npos;
74 } else {
75 // we've just looked past a trailing delimiter
76 248 break;
77 }
78 } else {
79
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2015 times.
2015 assert(sep_pos >= entry_pos);
80 2015 entry_length = sep_pos - entry_pos;
81 2015 next_pos = sep_pos + 1;
82 }
83
2/2
✓ Branch 0 taken 341 times.
✓ Branch 1 taken 2108 times.
2449 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 341 entry_pos = next_pos;
87 341 continue;
88 }
89
1/2
✓ Branch 1 taken 2108 times.
✗ Branch 2 not taken.
2108 string entry(acl_string, entry_pos, entry_length);
90 2108 entry_pos = next_pos;
91
92 // search for '#'-starting comment, discard if found
93 2108 size_t const comment_pos = entry.find('#');
94
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2108 times.
2108 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 2108 times.
2108 if (entry.length() == 0) {
102 continue;
103 }
104
105
1/2
✓ Branch 1 taken 2108 times.
✗ Branch 2 not taken.
2108 string_entries.push_back(entry);
106
1/2
✓ Branch 1 taken 2108 times.
✗ Branch 2 not taken.
2108 }
107 682 return 0;
108 }
109
110 1922 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 1922 *perms = 0;
117
2/2
✓ Branch 5 taken 3472 times.
✓ Branch 6 taken 1922 times.
5394 for (const char &c : str) {
118
4/5
✓ Branch 0 taken 1271 times.
✓ Branch 1 taken 775 times.
✓ Branch 2 taken 372 times.
✓ Branch 3 taken 1054 times.
✗ Branch 4 not taken.
3472 switch (c) {
119 1271 case 'r':
120 1271 *perms |= ACL_READ;
121 1271 break;
122 775 case 'w':
123 775 *perms |= ACL_WRITE;
124 775 break;
125 372 case 'x':
126 372 *perms |= ACL_EXECUTE;
127 372 break;
128 1054 case '-':
129 1054 break;
130 default:
131 return EINVAL;
132 }
133 }
134 1922 return 0;
135 }
136
137 1953 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 1953 size_t sep_pos = str.find(':');
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1953 times.
1953 if (sep_pos == string::npos) {
143 return EINVAL;
144 }
145
1/2
✓ Branch 1 taken 1953 times.
✗ Branch 2 not taken.
1953 string const type(str, 0, sep_pos);
146 1953 size_t next_field_pos = sep_pos + 1;
147 1953 sep_pos = str.find(':', next_field_pos);
148
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1953 times.
1953 if (sep_pos == string::npos) {
149 return EINVAL;
150 }
151
1/2
✓ Branch 1 taken 1953 times.
✗ Branch 2 not taken.
1953 string const qualifier(str, next_field_pos, sep_pos - next_field_pos);
152 1953 next_field_pos = sep_pos + 1;
153
1/2
✓ Branch 2 taken 1953 times.
✗ Branch 3 not taken.
1953 string const permissions(str, next_field_pos);
154
155
6/6
✓ Branch 1 taken 1922 times.
✓ Branch 2 taken 31 times.
✓ Branch 4 taken 744 times.
✓ Branch 5 taken 1178 times.
✓ Branch 6 taken 775 times.
✓ Branch 7 taken 1178 times.
1953 if (!type.compare("user") || !type.compare("u")) {
156
2/2
✓ Branch 1 taken 465 times.
✓ Branch 2 taken 310 times.
775 entry.e_tag = qualifier.empty() ? ACL_USER_OBJ : ACL_USER;
157
6/6
✓ Branch 1 taken 1085 times.
✓ Branch 2 taken 93 times.
✓ Branch 4 taken 465 times.
✓ Branch 5 taken 620 times.
✓ Branch 6 taken 558 times.
✓ Branch 7 taken 620 times.
1178 } else if (!type.compare("group") || !type.compare("g")) {
158
2/2
✓ Branch 1 taken 434 times.
✓ Branch 2 taken 124 times.
558 entry.e_tag = qualifier.empty() ? ACL_GROUP_OBJ : ACL_GROUP;
159
6/6
✓ Branch 1 taken 589 times.
✓ Branch 2 taken 31 times.
✓ Branch 4 taken 403 times.
✓ Branch 5 taken 186 times.
✓ Branch 6 taken 434 times.
✓ Branch 7 taken 186 times.
620 } else if (!type.compare("other") || !type.compare("o")) {
160 434 entry.e_tag = ACL_OTHER;
161
6/6
✓ Branch 1 taken 93 times.
✓ Branch 2 taken 93 times.
✓ Branch 4 taken 62 times.
✓ Branch 5 taken 31 times.
✓ Branch 6 taken 155 times.
✓ Branch 7 taken 31 times.
186 } else if (!type.compare("mask") || !type.compare("m")) {
162 155 entry.e_tag = ACL_MASK;
163 } else {
164 31 return EINVAL;
165 }
166 1922 entry.e_tag = htole16(entry.e_tag);
167
168
2/2
✓ Branch 1 taken 1488 times.
✓ Branch 2 taken 434 times.
1922 if (qualifier.empty()) {
169 1488 entry.e_id = ACL_UNDEFINED_ID;
170 } else {
171 char *at_null_terminator_if_number;
172 434 long number = strtol(qualifier.c_str(), &at_null_terminator_if_number, 10);
173
2/2
✓ Branch 0 taken 217 times.
✓ Branch 1 taken 217 times.
434 if (*at_null_terminator_if_number != '\0') {
174 bool ok;
175
2/2
✓ Branch 1 taken 155 times.
✓ Branch 2 taken 62 times.
217 if (entry.e_tag == htole16(ACL_USER)) {
176 [[maybe_unused]] gid_t main_gid;
177 uid_t uid;
178
1/2
✓ Branch 1 taken 155 times.
✗ Branch 2 not taken.
155 ok = GetUidOf(qualifier, &uid, &main_gid);
179 155 number = uid;
180
1/2
✓ Branch 1 taken 62 times.
✗ Branch 2 not taken.
62 } else if (entry.e_tag == htole16(ACL_GROUP)) {
181 gid_t gid;
182
1/2
✓ Branch 1 taken 62 times.
✗ Branch 2 not taken.
62 ok = GetGidOf(qualifier, &gid);
183 62 number = gid;
184 } else {
185 assert(false);
186 }
187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 217 times.
217 if (!ok) {
188 return EINVAL;
189 }
190 }
191 434 entry.e_id = htole32(number);
192 }
193
194 // parse perms
195 u_int16_t host_byteorder_perms;
196 int ret;
197 1922 ret = acl_parms_from_text(permissions, &host_byteorder_perms);
198
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1922 times.
1922 if (ret) {
199 return ret;
200 }
201 1922 entry.e_perm = htole16(host_byteorder_perms);
202
203 1922 return 0;
204 1953 }
205
206 651 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 651 bool types_met[ACL_OTHER + 1] = {
220 false,
221 };
222
223
2/2
✓ Branch 4 taken 1798 times.
✓ Branch 5 taken 620 times.
2418 for (auto entry_it = entries.begin(); entry_it != entries.end(); ++entry_it) {
224 1798 const acl_ea_entry &e = *entry_it;
225
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1798 times.
1798 assert(e.e_tag <= ACL_OTHER);
226 1798 bool &type_met = types_met[e.e_tag];
227
2/3
✓ Branch 0 taken 1426 times.
✓ Branch 1 taken 372 times.
✗ Branch 2 not taken.
1798 switch (e.e_tag) {
228 // at most one of these types
229 1426 case ACL_USER_OBJ:
230 case ACL_GROUP_OBJ:
231 case ACL_OTHER:
232 case ACL_MASK:
233
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 1395 times.
1426 if (type_met) {
234 31 return false;
235 } else {
236 1395 type_met = true;
237 }
238 1395 break;
239 372 case ACL_USER:
240 case ACL_GROUP:
241 372 type_met = true;
242 372 break;
243 default:
244 assert(false);
245 }
246 }
247
4/4
✓ Branch 0 taken 403 times.
✓ Branch 1 taken 217 times.
✓ Branch 2 taken 372 times.
✓ Branch 3 taken 31 times.
620 if (!(types_met[ACL_USER_OBJ] && types_met[ACL_GROUP_OBJ]
248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 372 times.
372 && types_met[ACL_OTHER])) {
249 248 return false;
250 }
251
6/6
✓ Branch 0 taken 217 times.
✓ Branch 1 taken 155 times.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 186 times.
✓ Branch 4 taken 31 times.
✓ Branch 5 taken 155 times.
372 if ((types_met[ACL_USER] || types_met[ACL_GROUP]) && !types_met[ACL_MASK]) {
252 31 return false;
253 }
254 // TODO(autkin): ACL_USER, ACL_GROUP uniqueness checks. Not a pressing issue.
255 341 return true;
256 }
257
258 682 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 682 o_equiv_mode = true;
263
264 // get individual textual entries from one big text
265 682 vector<string> string_entries;
266
1/2
✓ Branch 1 taken 682 times.
✗ Branch 2 not taken.
682 ret = acl_from_text_to_string_entries(textual_acl, string_entries);
267
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 682 times.
682 if (ret) {
268 return ret;
269 }
270
271 // get individual entries in structural form
272 682 vector<acl_ea_entry> entries;
273 682 for (auto string_it = string_entries.begin();
274
2/2
✓ Branch 2 taken 1953 times.
✓ Branch 3 taken 651 times.
2604 string_it != string_entries.end();
275 1922 ++string_it) {
276 acl_ea_entry entry;
277
1/2
✓ Branch 2 taken 1953 times.
✗ Branch 3 not taken.
1953 ret = acl_entry_from_text(*string_it, entry);
278
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 1922 times.
1953 if (ret) {
279 31 return ret;
280 }
281
4/4
✓ Branch 0 taken 1798 times.
✓ Branch 1 taken 124 times.
✓ Branch 2 taken 310 times.
✓ Branch 3 taken 1488 times.
1922 if (entry.e_tag == ACL_GROUP || entry.e_tag == ACL_USER) {
282 434 o_equiv_mode = false;
283 }
284
1/2
✓ Branch 1 taken 1922 times.
✗ Branch 2 not taken.
1922 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 651 times.
✗ Branch 4 not taken.
651 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 310 times.
✓ Branch 2 taken 341 times.
651 if (!acl_valid_builtin(entries)) {
292 310 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 186 times.
✓ Branch 1 taken 155 times.
341 if (o_equiv_mode) {
297 186 o_binary_acl = NULL;
298 186 o_size = 0;
299 186 return 0;
300 }
301
302 // get one big buffer with all the entries in the "on-disk" xattr format
303 155 size_t const acl_entry_count = entries.size();
304 155 size_t const buf_size = sizeof(acl_ea_header)
305 155 + (acl_entry_count * sizeof(acl_ea_entry));
306 155 char *buf = static_cast<char *>(malloc(buf_size));
307
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 155 times.
155 if (!buf) {
308 return ENOMEM;
309 }
310 155 acl_ea_header *header = reinterpret_cast<acl_ea_header *>(buf);
311 155 header->a_version = htole32(ACL_EA_VERSION);
312 155 acl_ea_entry *ext_entry = reinterpret_cast<acl_ea_entry *>(header + 1);
313
2/2
✓ Branch 3 taken 899 times.
✓ Branch 4 taken 155 times.
1054 for (auto entry_it = entries.begin(); entry_it != entries.end(); ++entry_it) {
314 899 *ext_entry = *entry_it;
315 899 ext_entry += 1;
316 }
317
318 155 o_binary_acl = buf;
319 155 o_size = buf_size;
320 155 return 0;
321 682 }
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