GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/acl.cc
Date: 2026-08-09 02:40:25
Exec Total Coverage
Lines: 135 151 89.4%
Branches: 101 132 76.5%

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