GCC Code Coverage Report


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