parse-util: Add parse_capability_set()
Let's extract common capability parsing code into a generic function parse_capability_set() with a comprehensive set of unit tests. We also replace usages of UINT64_MAX with CAP_MASK_UNSET where applicable and replace the default value of CapabilityBoundingSet with CAP_MASK_ALL which more clearly identifies that it is initialized to all capabilities. AI (copilot) was used to extract the generic function and write the unit tests, with manual review and fixing afterwards to make sure everything was correct.
This commit is contained in:
committed by
Yu Watanabe
parent
1a3b3c57c6
commit
10e82fde7b
@@ -5,6 +5,7 @@
|
||||
#include <math.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "capability-util.h"
|
||||
#include "locale-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "tests.h"
|
||||
@@ -888,4 +889,127 @@ TEST(nft_identifier_valid) {
|
||||
ASSERT_FALSE(nft_identifier_valid(s));
|
||||
}
|
||||
|
||||
static uint64_t make_cap(int cap) {
|
||||
return ((uint64_t) 1ULL << (uint64_t) cap);
|
||||
}
|
||||
|
||||
TEST(parse_capability_set) {
|
||||
uint64_t current;
|
||||
|
||||
/* Empty string resets to CAP_MASK_UNSET */
|
||||
current = 0x1234;
|
||||
ASSERT_OK(parse_capability_set("", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, CAP_MASK_UNSET);
|
||||
|
||||
/* Single capability by name - replaces if current == initial */
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK(parse_capability_set("cap_chown", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN));
|
||||
|
||||
/* Single capability by name - merges if current != initial */
|
||||
current = make_cap(CAP_SETUID);
|
||||
ASSERT_OK(parse_capability_set("cap_chown", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN) | make_cap(CAP_SETUID));
|
||||
|
||||
/* Multiple capabilities - replaces when current == initial */
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK(parse_capability_set("cap_chown cap_setuid", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN) | make_cap(CAP_SETUID));
|
||||
|
||||
/* Multiple capabilities - merges when current != initial */
|
||||
current = make_cap(CAP_SETGID);
|
||||
ASSERT_OK(parse_capability_set("cap_chown cap_setuid", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN) | make_cap(CAP_SETUID) | make_cap(CAP_SETGID));
|
||||
|
||||
/* Inverted capabilities - replaces with complement when current == initial */
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK(parse_capability_set("~cap_chown", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, all_capabilities() & ~make_cap(CAP_CHOWN));
|
||||
|
||||
/* Inverted capabilities - removes from current when current != initial */
|
||||
current = all_capabilities();
|
||||
ASSERT_OK(parse_capability_set("~cap_chown", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, all_capabilities() & ~make_cap(CAP_CHOWN));
|
||||
|
||||
/* Inverted multiple capabilities */
|
||||
current = all_capabilities();
|
||||
ASSERT_OK(parse_capability_set("~cap_chown cap_setuid", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, all_capabilities() & ~(make_cap(CAP_CHOWN) | make_cap(CAP_SETUID)));
|
||||
|
||||
/* Tilde alone resets to all capabilities complement (i.e., empty) */
|
||||
current = 0x1234;
|
||||
ASSERT_OK(parse_capability_set("~", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, all_capabilities());
|
||||
|
||||
/* Sequential calls - testing merge behavior */
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK(parse_capability_set("cap_chown", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN));
|
||||
ASSERT_OK(parse_capability_set("cap_setuid", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN) | make_cap(CAP_SETUID));
|
||||
|
||||
/* Sequential calls with invert */
|
||||
current = all_capabilities();
|
||||
ASSERT_OK(parse_capability_set("~cap_chown", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_OK(parse_capability_set("~cap_setuid", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, all_capabilities() & ~(make_cap(CAP_CHOWN) | make_cap(CAP_SETUID)));
|
||||
|
||||
/* Numeric capability */
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK(parse_capability_set("0", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(0));
|
||||
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK(parse_capability_set("5", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(5));
|
||||
|
||||
/* Mixed numeric and named capabilities */
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK(parse_capability_set("0 cap_chown 5", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(0) | make_cap(CAP_CHOWN) | make_cap(5));
|
||||
|
||||
/* Invalid capabilities are ignored but function returns 0 */
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK_ZERO(parse_capability_set("invalid_cap", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, 0U);
|
||||
|
||||
/* Mix of valid and invalid capabilities */
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK_ZERO(parse_capability_set("cap_chown invalid_cap cap_setuid", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN) | make_cap(CAP_SETUID));
|
||||
|
||||
/* Case insensitivity */
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK(parse_capability_set("CAP_CHOWN", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN));
|
||||
|
||||
current = CAP_MASK_UNSET;
|
||||
ASSERT_OK(parse_capability_set("CaP_ChOwN", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN));
|
||||
|
||||
/* Inverted with invalid capabilities */
|
||||
current = all_capabilities();
|
||||
ASSERT_OK_ZERO(parse_capability_set("~invalid_cap", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, all_capabilities());
|
||||
|
||||
/* Inverted with mix of valid and invalid */
|
||||
current = all_capabilities();
|
||||
ASSERT_OK_ZERO(parse_capability_set("~cap_chown invalid_cap", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, all_capabilities() & ~make_cap(CAP_CHOWN));
|
||||
|
||||
/* Whitespace handling */
|
||||
current = 0;
|
||||
ASSERT_OK(parse_capability_set(" cap_chown cap_setuid ", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN) | make_cap(CAP_SETUID));
|
||||
|
||||
/* Testing that initial value determines replace vs merge */
|
||||
current = make_cap(CAP_SETGID);
|
||||
ASSERT_OK(parse_capability_set("cap_chown", make_cap(CAP_SETGID), ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN)); /* Replace because current == initial */
|
||||
|
||||
current = make_cap(CAP_SETGID);
|
||||
ASSERT_OK(parse_capability_set("cap_chown", CAP_MASK_UNSET, ¤t));
|
||||
ASSERT_EQ(current, make_cap(CAP_CHOWN) | make_cap(CAP_SETGID)); /* Merge because current != initial */
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
||||
Reference in New Issue
Block a user