GCC Code Coverage Report


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