GCC Code Coverage Report


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