GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/acl.cc
Date: 2026-03-15 02:35:27
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 3813 bool operator<(const acl_ea_entry &other) const {
57
2/2
✓ Branch 0 taken 3567 times.
✓ Branch 1 taken 246 times.
3813 if (e_tag != other.e_tag) {
58 3567 return e_tag < other.e_tag;
59 }
60 246 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 902 static int acl_from_text_to_string_entries(const string &acl_string,
70 vector<string> &string_entries) {
71 902 std::size_t entry_pos = 0;
72
2/2
✓ Branch 0 taken 3567 times.
✓ Branch 1 taken 574 times.
4141 while (entry_pos != string::npos) {
73 size_t entry_length;
74 size_t next_pos;
75 3567 size_t const sep_pos = acl_string.find_first_of(",\n", entry_pos);
76
2/2
✓ Branch 0 taken 902 times.
✓ Branch 1 taken 2665 times.
3567 if (sep_pos == string::npos) {
77
2/2
✓ Branch 1 taken 574 times.
✓ Branch 2 taken 328 times.
902 if (acl_string.length() > entry_pos) {
78 574 entry_length = acl_string.length() - entry_pos;
79 574 next_pos = string::npos;
80 } else {
81 // we've just looked past a trailing delimiter
82 328 break;
83 }
84 } else {
85
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2665 times.
2665 assert(sep_pos >= entry_pos);
86 2665 entry_length = sep_pos - entry_pos;
87 2665 next_pos = sep_pos + 1;
88 }
89
2/2
✓ Branch 0 taken 451 times.
✓ Branch 1 taken 2788 times.
3239 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 451 entry_pos = next_pos;
93 451 continue;
94 }
95
1/2
✓ Branch 1 taken 2788 times.
✗ Branch 2 not taken.
2788 string entry(acl_string, entry_pos, entry_length);
96 2788 entry_pos = next_pos;
97
98 // search for '#'-starting comment, discard if found
99 2788 size_t const comment_pos = entry.find('#');
100
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2788 times.
2788 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 2788 times.
2788 if (entry.length() == 0) {
108 continue;
109 }
110
111
1/2
✓ Branch 1 taken 2788 times.
✗ Branch 2 not taken.
2788 string_entries.push_back(entry);
112
1/2
✓ Branch 1 taken 2788 times.
✗ Branch 2 not taken.
2788 }
113 902 return 0;
114 }
115
116 2542 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 2542 *perms = 0;
123
2/2
✓ Branch 5 taken 4592 times.
✓ Branch 6 taken 2542 times.
7134 for (const char &c : str) {
124
4/5
✓ Branch 0 taken 1681 times.
✓ Branch 1 taken 1025 times.
✓ Branch 2 taken 492 times.
✓ Branch 3 taken 1394 times.
✗ Branch 4 not taken.
4592 switch (c) {
125 1681 case 'r':
126 1681 *perms |= ACL_READ;
127 1681 break;
128 1025 case 'w':
129 1025 *perms |= ACL_WRITE;
130 1025 break;
131 492 case 'x':
132 492 *perms |= ACL_EXECUTE;
133 492 break;
134 1394 case '-':
135 1394 break;
136 default:
137 return EINVAL;
138 }
139 }
140 2542 return 0;
141 }
142
143 2583 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 2583 size_t sep_pos = str.find(':');
148
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2583 times.
2583 if (sep_pos == string::npos) {
149 return EINVAL;
150 }
151
1/2
✓ Branch 1 taken 2583 times.
✗ Branch 2 not taken.
2583 string const type(str, 0, sep_pos);
152 2583 size_t next_field_pos = sep_pos + 1;
153 2583 sep_pos = str.find(':', next_field_pos);
154
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2583 times.
2583 if (sep_pos == string::npos) {
155 return EINVAL;
156 }
157
1/2
✓ Branch 1 taken 2583 times.
✗ Branch 2 not taken.
2583 string const qualifier(str, next_field_pos, sep_pos - next_field_pos);
158 2583 next_field_pos = sep_pos + 1;
159
1/2
✓ Branch 2 taken 2583 times.
✗ Branch 3 not taken.
2583 string const permissions(str, next_field_pos);
160
161
6/6
✓ Branch 1 taken 2542 times.
✓ Branch 2 taken 41 times.
✓ Branch 4 taken 984 times.
✓ Branch 5 taken 1558 times.
✓ Branch 6 taken 1025 times.
✓ Branch 7 taken 1558 times.
2583 if (!type.compare("user") || !type.compare("u")) {
162
2/2
✓ Branch 1 taken 615 times.
✓ Branch 2 taken 410 times.
1025 entry.e_tag = qualifier.empty() ? ACL_USER_OBJ : ACL_USER;
163
6/6
✓ Branch 1 taken 1435 times.
✓ Branch 2 taken 123 times.
✓ Branch 4 taken 615 times.
✓ Branch 5 taken 820 times.
✓ Branch 6 taken 738 times.
✓ Branch 7 taken 820 times.
1558 } else if (!type.compare("group") || !type.compare("g")) {
164
2/2
✓ Branch 1 taken 574 times.
✓ Branch 2 taken 164 times.
738 entry.e_tag = qualifier.empty() ? ACL_GROUP_OBJ : ACL_GROUP;
165
6/6
✓ Branch 1 taken 779 times.
✓ Branch 2 taken 41 times.
✓ Branch 4 taken 533 times.
✓ Branch 5 taken 246 times.
✓ Branch 6 taken 574 times.
✓ Branch 7 taken 246 times.
820 } else if (!type.compare("other") || !type.compare("o")) {
166 574 entry.e_tag = ACL_OTHER;
167
6/6
✓ Branch 1 taken 123 times.
✓ Branch 2 taken 123 times.
✓ Branch 4 taken 82 times.
✓ Branch 5 taken 41 times.
✓ Branch 6 taken 205 times.
✓ Branch 7 taken 41 times.
246 } else if (!type.compare("mask") || !type.compare("m")) {
168 205 entry.e_tag = ACL_MASK;
169 } else {
170 41 return EINVAL;
171 }
172 2542 entry.e_tag = htole16(entry.e_tag);
173
174
2/2
✓ Branch 1 taken 1968 times.
✓ Branch 2 taken 574 times.
2542 if (qualifier.empty()) {
175 1968 entry.e_id = ACL_UNDEFINED_ID;
176 } else {
177 char *at_null_terminator_if_number;
178 574 long number = strtol(qualifier.c_str(), &at_null_terminator_if_number, 10);
179
2/2
✓ Branch 0 taken 205 times.
✓ Branch 1 taken 369 times.
574 if (*at_null_terminator_if_number != '\0') {
180 bool ok;
181
1/2
✓ Branch 1 taken 205 times.
✗ Branch 2 not taken.
205 if (entry.e_tag == htole16(ACL_USER)) {
182 [[maybe_unused]] gid_t main_gid;
183 uid_t uid;
184
1/2
✓ Branch 1 taken 205 times.
✗ Branch 2 not taken.
205 ok = GetUidOf(qualifier, &uid, &main_gid);
185 205 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 205 times.
205 if (!ok) {
194 return EINVAL;
195 }
196 }
197 574 entry.e_id = htole32(number);
198 }
199
200 // parse perms
201 u_int16_t host_byteorder_perms;
202 int ret;
203 2542 ret = acl_parms_from_text(permissions, &host_byteorder_perms);
204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2542 times.
2542 if (ret) {
205 return ret;
206 }
207 2542 entry.e_perm = htole16(host_byteorder_perms);
208
209 2542 return 0;
210 2583 }
211
212 861 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 861 bool types_met[ACL_OTHER + 1] = {
226 false,
227 };
228
229
2/2
✓ Branch 4 taken 2378 times.
✓ Branch 5 taken 820 times.
3198 for (auto entry_it = entries.begin(); entry_it != entries.end(); ++entry_it) {
230 2378 const acl_ea_entry &e = *entry_it;
231
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2378 times.
2378 assert(e.e_tag <= ACL_OTHER);
232 2378 bool &type_met = types_met[e.e_tag];
233
2/3
✓ Branch 0 taken 1886 times.
✓ Branch 1 taken 492 times.
✗ Branch 2 not taken.
2378 switch (e.e_tag) {
234 // at most one of these types
235 1886 case ACL_USER_OBJ:
236 case ACL_GROUP_OBJ:
237 case ACL_OTHER:
238 case ACL_MASK:
239
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 1845 times.
1886 if (type_met) {
240 41 return false;
241 } else {
242 1845 type_met = true;
243 }
244 1845 break;
245 492 case ACL_USER:
246 case ACL_GROUP:
247 492 type_met = true;
248 492 break;
249 default:
250 assert(false);
251 }
252 }
253
4/4
✓ Branch 0 taken 533 times.
✓ Branch 1 taken 287 times.
✓ Branch 2 taken 492 times.
✓ Branch 3 taken 41 times.
820 if (!(types_met[ACL_USER_OBJ] && types_met[ACL_GROUP_OBJ]
254
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 492 times.
492 && types_met[ACL_OTHER])) {
255 328 return false;
256 }
257
6/6
✓ Branch 0 taken 287 times.
✓ Branch 1 taken 205 times.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 246 times.
✓ Branch 4 taken 41 times.
✓ Branch 5 taken 205 times.
492 if ((types_met[ACL_USER] || types_met[ACL_GROUP]) && !types_met[ACL_MASK]) {
258 41 return false;
259 }
260 // TODO(autkin): ACL_USER, ACL_GROUP uniqueness checks. Not a pressing issue.
261 451 return true;
262 }
263
264 902 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 902 o_equiv_mode = true;
269
270 // get individual textual entries from one big text
271 902 vector<string> string_entries;
272
1/2
✓ Branch 1 taken 902 times.
✗ Branch 2 not taken.
902 ret = acl_from_text_to_string_entries(textual_acl, string_entries);
273
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 902 times.
902 if (ret) {
274 return ret;
275 }
276
277 // get individual entries in structural form
278 902 vector<acl_ea_entry> entries;
279 902 for (auto string_it = string_entries.begin();
280
2/2
✓ Branch 2 taken 2583 times.
✓ Branch 3 taken 861 times.
3444 string_it != string_entries.end();
281 2542 ++string_it) {
282 acl_ea_entry entry;
283
1/2
✓ Branch 2 taken 2583 times.
✗ Branch 3 not taken.
2583 ret = acl_entry_from_text(*string_it, entry);
284
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 2542 times.
2583 if (ret) {
285 41 return ret;
286 }
287
4/4
✓ Branch 0 taken 2378 times.
✓ Branch 1 taken 164 times.
✓ Branch 2 taken 410 times.
✓ Branch 3 taken 1968 times.
2542 if (entry.e_tag == ACL_GROUP || entry.e_tag == ACL_USER) {
288 574 o_equiv_mode = false;
289 }
290
1/2
✓ Branch 1 taken 2542 times.
✗ Branch 2 not taken.
2542 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 861 times.
✗ Branch 4 not taken.
861 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 410 times.
✓ Branch 2 taken 451 times.
861 if (!acl_valid_builtin(entries)) {
298 410 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 246 times.
✓ Branch 1 taken 205 times.
451 if (o_equiv_mode) {
303 246 o_binary_acl = NULL;
304 246 o_size = 0;
305 246 return 0;
306 }
307
308 // get one big buffer with all the entries in the "on-disk" xattr format
309 205 size_t const acl_entry_count = entries.size();
310 205 size_t const buf_size = sizeof(acl_ea_header)
311 205 + (acl_entry_count * sizeof(acl_ea_entry));
312 205 char *buf = static_cast<char *>(malloc(buf_size));
313
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 205 times.
205 if (!buf) {
314 return ENOMEM;
315 }
316 205 acl_ea_header *header = reinterpret_cast<acl_ea_header *>(buf);
317 205 header->a_version = htole32(ACL_EA_VERSION);
318 205 acl_ea_entry *ext_entry = reinterpret_cast<acl_ea_entry *>(header + 1);
319
2/2
✓ Branch 3 taken 1189 times.
✓ Branch 4 taken 205 times.
1394 for (auto entry_it = entries.begin(); entry_it != entries.end(); ++entry_it) {
320 1189 *ext_entry = *entry_it;
321 1189 ext_entry += 1;
322 }
323
324 205 o_binary_acl = buf;
325 205 o_size = buf_size;
326 205 return 0;
327 902 }
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