Skip to content

Commit 4913f36

Browse files
drwezCQ Bot
authored andcommitted
[sestarnix] Cleanup the userspace tests & framework a little
- Refactor the various attribute and SELinux API reading helpers for greater code re-use. - Introduce a matcher for fit::result<int,string> values, to allow tests to write e.g. EXPECT_THAT(foo, IsOk(value)). - Provide a PrintTo() overload for fit::result<E,T>, to allow tests to get nice output on failure from the IsOk() matcher. Change-Id: I1330f606948323ac5d3b87d4a4f842170b42d764 Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1236086 Reviewed-by: Ambre Williams <[email protected]> Commit-Queue: Wez <[email protected]> Fuchsia-Auto-Submit: Wez <[email protected]>
1 parent 07fc45f commit 4913f36

File tree

6 files changed

+85
-113
lines changed

6 files changed

+85
-113
lines changed

src/starnix/tests/selinux/userspace/tests/load_policy.cc

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,13 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
#include <sys/xattr.h>
6-
#include <unistd.h>
7-
8-
#include <string>
9-
105
#include <gtest/gtest.h>
116

127
#include "src/starnix/tests/selinux/userspace/util.h"
138

14-
// Linux inserts a mysterious '\0' at the end of the label in /proc/<pid>/attr/current, SEStarnix
15-
// currently doesn't.
16-
std::string RemoveTailNull(std::string in) {
17-
if (in.size() > 0 && in[in.size() - 1] == 0) {
18-
in.pop_back();
19-
}
20-
return in;
21-
}
22-
239
TEST(PolicyLoadTest, TasksUseKernelSid) {
2410
LoadPolicy("minimal_policy.pp");
2511

26-
std::string s = ReadFile("/proc/thread-self/attr/current");
2712
// All processes created prior to policy loading are labeled with the kernel SID.
28-
EXPECT_EQ(RemoveTailNull(ReadFile("/proc/thread-self/attr/current")),
29-
"system_u:unconfined_r:unconfined_t:s0");
13+
EXPECT_THAT(ReadTaskAttr("current"), IsOk("system_u:unconfined_r:unconfined_t:s0"));
3014
}

src/starnix/tests/selinux/userspace/tests/pipe.cc

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
#include <sys/xattr.h>
65
#include <unistd.h>
76

87
#include <string>
@@ -17,20 +16,12 @@ TEST(PolicyLoadTest, Pipes) {
1716
EXPECT_THAT(pipe(pipe_before_policy), SyscallSucceeds());
1817
LoadPolicy("minimal_policy.pp");
1918

20-
ssize_t len = 0;
21-
char label[256] = {};
22-
EXPECT_NE((len = fgetxattr(pipe_before_policy[0], "security.selinux", label, sizeof(label))), -1);
23-
// TODO: https://fxbug.dev/395625171 - This ignores the final '\0' added by Linux.
24-
EXPECT_EQ(std::string(label, len).c_str(), std::string("system_u:unconfined_r:unconfined_t:s0"));
19+
EXPECT_THAT(pipe_before_policy[0], FdIsLabeled("system_u:unconfined_r:unconfined_t:s0"));
2520

2621
WriteContents("/proc/thread-self/attr/current", "system_u:unconfined_r:unconfined_t:s0");
2722

2823
int pipe_after_policy[2];
2924
EXPECT_THAT(pipe(pipe_after_policy), SyscallSucceeds());
3025

31-
len = 0;
32-
EXPECT_THAT((len = fgetxattr(pipe_after_policy[0], "security.selinux", label, sizeof(label))),
33-
SyscallSucceeds());
34-
// TODO: https://fxbug.dev/395625171 - This ignores the final '\0' added by Linux.
35-
EXPECT_EQ(std::string(label, len).c_str(), std::string("system_u:unconfined_r:unconfined_t:s0"));
26+
EXPECT_THAT(pipe_after_policy[0], FdIsLabeled("system_u:unconfined_r:unconfined_t:s0"));
3627
}

src/starnix/tests/selinux/userspace/tests/procattr.cc

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,17 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
#include <fcntl.h>
6-
#include <lib/fit/result.h>
7-
#include <unistd.h>
8-
9-
#include <ostream>
10-
#include <string>
11-
12-
#include <fbl/unique_fd.h>
135
#include <gtest/gtest.h>
14-
#include <linux/fs.h>
156

167
#include "src/starnix/tests/selinux/userspace/util.h"
178

189
namespace {
1910

20-
using ValidateContextResult = fit::result<int, std::string>;
21-
22-
ValidateContextResult get_procattr(std::string_view attr_name) {
23-
constexpr char procattr_prefix[] = "/proc/self/attr/";
24-
std::string attr_path(procattr_prefix);
25-
attr_path.append(attr_name);
26-
fbl::unique_fd attr_api(open(attr_path.c_str(), O_RDONLY));
27-
if (!attr_api.is_valid()) {
28-
return fit::error(errno);
29-
}
30-
std::string attr_value;
31-
char read_buf[10];
32-
while (true) {
33-
ssize_t result = read(attr_api.get(), read_buf, sizeof(read_buf));
34-
if (result == 0) {
35-
return fit::ok(attr_value);
36-
}
37-
if (result < 0) {
38-
return fit::error(errno);
39-
}
40-
attr_value.append(read_buf, result);
41-
}
42-
}
43-
4411
TEST(ProcAttr, Current) {
4512
LoadPolicy("minimal_policy.pp");
4613

4714
// Attempting to read the process' current context should return a value.
48-
auto current = get_procattr("current");
49-
EXPECT_TRUE(current.is_ok()) << "Unable to read 'current':" << current.error_value();
15+
EXPECT_THAT(ReadTaskAttr("current"), IsOk("system_u:unconfined_r:unconfined_t:s0"));
5016
}
5117

5218
} // namespace

src/starnix/tests/selinux/userspace/tests/selinuxfs.cc

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,11 @@
1515

1616
#include "src/starnix/tests/selinux/userspace/util.h"
1717

18-
// TODO: Pretty-print without polluting the global namespace!
19-
namespace fit {
20-
void PrintTo(const fit::result<int, std::string>& value, std::ostream* os) {
21-
if (value.is_ok()) {
22-
*os << "Ok(\"" << value.value() << "\")";
23-
} else {
24-
*os << "Errno(" << value.error_value() << ")";
25-
}
26-
}
27-
} // namespace fit
28-
2918
namespace {
3019

3120
using ValidateContextResult = fit::result<int, std::string>;
3221

33-
ValidateContextResult validate_context(std::string_view context) {
22+
ValidateContextResult ValidateContext(std::string_view context) {
3423
constexpr char context_api_path[] = "/sys/fs/selinux/context";
3524
fbl::unique_fd context_api(open(context_api_path, O_RDWR));
3625
if (!context_api.is_valid()) {
@@ -45,7 +34,7 @@ ValidateContextResult validate_context(std::string_view context) {
4534
ssize_t result = read(context_api.get(), read_buf, sizeof(read_buf));
4635
if (result == 0) {
4736
// Use `c_str()` to strip the trailing NUL, if any, from the read context.
48-
return fit::ok(validated_context.c_str());
37+
return fit::ok(RemoveTrailingNul(validated_context));
4938
}
5039
if (result < 0) {
5140
return fit::error(errno);
@@ -60,14 +49,14 @@ TEST(SeLinuxFsContext, ValidatesRequiredFieldsPresent) {
6049
LoadPolicy("selinuxfs_policy.pp");
6150

6251
// Contexts that have too few colons to provide user, role, type & sensitivity are rejected.
63-
EXPECT_EQ(validate_context("test_selinuxfs_u"), fit::failed());
64-
EXPECT_EQ(validate_context("test_selinuxfs_u:test_selinuxfs_r"), fit::failed());
65-
EXPECT_EQ(validate_context("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t"), fit::failed());
52+
EXPECT_EQ(ValidateContext("test_selinuxfs_u"), fit::failed());
53+
EXPECT_EQ(ValidateContext("test_selinuxfs_u:test_selinuxfs_r"), fit::failed());
54+
EXPECT_EQ(ValidateContext("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t"), fit::failed());
6655

6756
// The minimum valid context has at least user, role, type and low/default sensitivity.
6857
constexpr std::string_view kMinimumValidContext =
6958
"test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s0";
70-
EXPECT_EQ(validate_context(kMinimumValidContext), expect_ok(kMinimumValidContext));
59+
EXPECT_EQ(ValidateContext(kMinimumValidContext), expect_ok(kMinimumValidContext));
7160
}
7261

7362
TEST(SeLinuxFsContext, ValidatesFieldValues) {
@@ -76,34 +65,34 @@ TEST(SeLinuxFsContext, ValidatesFieldValues) {
7665
// Valid contexts are successfully written, and can be read-back.
7766
constexpr std::string_view kValidContext =
7867
"test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s0:c0-s2:c0.c2";
79-
EXPECT_EQ(validate_context(kValidContext), expect_ok(kValidContext));
68+
EXPECT_EQ(ValidateContext(kValidContext), expect_ok(kValidContext));
8069

8170
// Context user must be defined by the policy.
82-
EXPECT_EQ(validate_context("bad_value:test_selinuxfs_r:test_selinuxfs_t:s0:c0-s2:c0.c2"),
71+
EXPECT_EQ(ValidateContext("bad_value:test_selinuxfs_r:test_selinuxfs_t:s0:c0-s2:c0.c2"),
8372
fit::failed());
8473

8574
// Context role must be defined by the policy.
86-
EXPECT_EQ(validate_context("test_selinuxfs_u:bad_value:test_selinuxfs_t:s0:c0-s2:c0.c2"),
75+
EXPECT_EQ(ValidateContext("test_selinuxfs_u:bad_value:test_selinuxfs_t:s0:c0-s2:c0.c2"),
8776
fit::failed());
8877

8978
// Context type/domain must be defined by the policy.
90-
EXPECT_EQ(validate_context("test_selinuxfs_u:test_selinuxfs_r:bad_value:s0:c0-s2:c0.c2"),
79+
EXPECT_EQ(ValidateContext("test_selinuxfs_u:test_selinuxfs_r:bad_value:s0:c0-s2:c0.c2"),
9180
fit::failed());
9281

9382
// Context low & high sensitivities must be defined by the policy.
9483
EXPECT_EQ(
95-
validate_context("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:bad_value:c0-s2:c0.c2"),
84+
ValidateContext("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:bad_value:c0-s2:c0.c2"),
9685
fit::failed());
9786
EXPECT_EQ(
98-
validate_context("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s0:c0-bad_value:c0.c2"),
87+
ValidateContext("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s0:c0-bad_value:c0.c2"),
9988
fit::failed());
10089

10190
// Context low & high categories must be defined by the policy.
10291
EXPECT_EQ(
103-
validate_context("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s0:bad_value-s2:c0.c2"),
92+
ValidateContext("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s0:bad_value-s2:c0.c2"),
10493
fit::failed());
10594
EXPECT_EQ(
106-
validate_context("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s0:c0-s2:c0.bad_value"),
95+
ValidateContext("test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s0:c0-s2:c0.bad_value"),
10796
fit::failed());
10897
}
10998

@@ -113,21 +102,21 @@ TEST(SeLinuxFsContext, ValidatesAllowedUserFieldValues) {
113102
// The "test_selinuxfs_u" user is granted the full range of categories.
114103
constexpr std::string_view kValidContext =
115104
"test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s0:c0-s2:c0.c2";
116-
EXPECT_EQ(validate_context(kValidContext), expect_ok(kValidContext));
105+
EXPECT_EQ(ValidateContext(kValidContext), expect_ok(kValidContext));
117106

118107
// The "test_selinuxfs_limited_u" user is granted only "s0" sensitivity, must have "c0" category
119108
// and may have "c1" category.
120109
constexpr std::string_view kLimitedContext_Valid =
121110
"test_selinuxfs_limited_level_u:test_selinuxfs_r:test_selinuxfs_t:s0:c0";
122-
EXPECT_EQ(validate_context(kLimitedContext_Valid), expect_ok(kLimitedContext_Valid));
111+
EXPECT_EQ(ValidateContext(kLimitedContext_Valid), expect_ok(kLimitedContext_Valid));
123112

124113
constexpr std::string_view kLimitedContext_MissingCategory =
125114
"test_selinuxfs_limited_level_u:test_selinuxfs_r:test_selinuxfs_t:s0";
126-
EXPECT_EQ(validate_context(kLimitedContext_MissingCategory), fit::failed());
115+
EXPECT_EQ(ValidateContext(kLimitedContext_MissingCategory), fit::failed());
127116

128117
constexpr std::string_view kLimitedContext_BadSensitivity =
129118
"test_selinuxfs_limited_level_u:test_selinuxfs_r:test_selinuxfs_t:s1:c0";
130-
EXPECT_EQ(validate_context(kLimitedContext_BadSensitivity), fit::failed());
119+
EXPECT_EQ(ValidateContext(kLimitedContext_BadSensitivity), fit::failed());
131120
}
132121

133122
TEST(SeLinuxFsContext, NormalizeCategories) {
@@ -139,15 +128,15 @@ TEST(SeLinuxFsContext, NormalizeCategories) {
139128
"test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s2:c0.c2";
140129
constexpr std::string_view kThreeCategoryContextFormB =
141130
"test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s2:c0,c1,c2";
142-
EXPECT_EQ(validate_context(kThreeCategoryContextFormA),
143-
validate_context(kThreeCategoryContextFormB));
131+
EXPECT_EQ(ValidateContext(kThreeCategoryContextFormA),
132+
ValidateContext(kThreeCategoryContextFormB));
144133

145134
// Using a pair of categories results in the same Security Context as a two-element range.
146135
constexpr std::string_view kTwoCategoryContextFormA =
147136
"test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s2:c0.c1";
148137
constexpr std::string_view kTwoCategoryContextFormB =
149138
"test_selinuxfs_u:test_selinuxfs_r:test_selinuxfs_t:s2:c0,c1";
150-
EXPECT_EQ(validate_context(kTwoCategoryContextFormA), validate_context(kTwoCategoryContextFormB));
139+
EXPECT_EQ(ValidateContext(kTwoCategoryContextFormA), ValidateContext(kTwoCategoryContextFormB));
151140
}
152141

153142
} // namespace

src/starnix/tests/selinux/userspace/util.cc

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,30 @@ std::string ReadFile(const std::string& name) {
5858
return contents;
5959
}
6060

61+
std::string RemoveTrailingNul(std::string in) {
62+
if (in.size() > 0 && in[in.size() - 1] == 0) {
63+
in.pop_back();
64+
}
65+
return in;
66+
}
67+
68+
fit::result<int, std::string> ReadTaskAttr(std::string_view attr_name) {
69+
constexpr char procattr_prefix[] = "/proc/self/attr/";
70+
std::string attr_path(procattr_prefix);
71+
attr_path.append(attr_name);
72+
73+
auto attr = ReadFile(attr_path);
74+
return fit::ok(RemoveTrailingNul(attr));
75+
}
76+
6177
fit::result<int, std::string> GetLabel(int fd) {
6278
char buf[256];
6379
ssize_t result = fgetxattr(fd, "security.selinux", buf, sizeof(buf));
6480
if (result < 0) {
6581
return fit::error(errno);
6682
}
6783
// Use `c_str()` to strip off the trailing NUL if present.
68-
return fit::ok(std::string(buf, result).c_str());
84+
return fit::ok(RemoveTrailingNul(std::string(buf, result)));
6985
}
7086

7187
ScopedEnforcement ScopedEnforcement::SetEnforcing() {

src/starnix/tests/selinux/userspace/util.h

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,30 @@
1212

1313
#include <gmock/gmock.h>
1414

15-
// Loads the policy |name|.
15+
/// Loads the policy |name|.
1616
void LoadPolicy(const std::string& name);
1717

18-
// Atomically writes |contents| to |file|, and fails the test otherwise.
18+
/// Atomically writes |contents| to |file|, and fails the test otherwise.
1919
void WriteContents(const std::string& file, const std::string& contents, bool create = false);
2020

21-
// Reads |file|, or fail the test.
21+
/// Reads |file|, or fail the test.
2222
std::string ReadFile(const std::string& file);
2323

24-
// Reads the security label of the specified `fd`, returning the `errno` on failure.
24+
/// Reads the specified security attribute (e.g. "current", "exec", etc) for the current task.
25+
fit::result<int, std::string> ReadTaskAttr(std::string_view attr_name);
26+
27+
/// Returns the `in`put string with the trailing NUL character, if any, removed.
28+
/// Some SELinux surfaces (e.g. "/proc/<pid/attr/<attr>") include the terminating NUL in the
29+
/// returned content under Linux, but not under SEStarnix.
30+
std::string RemoveTrailingNul(std::string in);
31+
32+
/// Reads the security label of the specified `fd`, returning the `errno` on failure.
33+
/// The trailing NUL, if any, will be stripped before the label is returned.
2534
fit::result<int, std::string> GetLabel(int fd);
2635

27-
// Runs the given action in a forked process after transitioning to |label|. This requires some
28-
// rules to be set-up. For transitions from unconfined_t (the starting label for tests), giving
29-
// them the `test_a` attribute from `test_policy.conf` is sufficient.
36+
/// Runs the given action in a forked process after transitioning to |label|. This requires some
37+
/// rules to be set-up. For transitions from unconfined_t (the starting label for tests), giving
38+
/// them the `test_a` attribute from `test_policy.conf` is sufficient.
3039
template <typename T>
3140
::testing::AssertionResult RunAs(const std::string& label, T action) {
3241
pid_t pid;
@@ -52,8 +61,8 @@ ::testing::AssertionResult RunAs(const std::string& label, T action) {
5261
}
5362
}
5463

55-
// Enables (or disables) enforcement while in scope, then restores enforcement to the previous
56-
// state.
64+
/// Enables (or disables) enforcement while in scope, then restores enforcement to the previous
65+
/// state.
5766
class ScopedEnforcement {
5867
public:
5968
static ScopedEnforcement SetEnforcing();
@@ -65,6 +74,15 @@ class ScopedEnforcement {
6574
std::string previous_state_;
6675
};
6776

77+
MATCHER_P(IsOk, expected_value, std::string("fit::result<> is fit::ok(") + expected_value + ")") {
78+
if (arg.is_error()) {
79+
*result_listener << "failed with error: " << arg.error_value();
80+
return false;
81+
}
82+
::testing::Matcher<fit::result<int, std::string>> expected = ::testing::Eq(expected_value);
83+
return expected.MatchAndExplain(arg, result_listener);
84+
}
85+
6886
MATCHER(SyscallSucceeds, "syscall succeeds") {
6987
if (arg != -1) {
7088
return true;
@@ -91,15 +109,23 @@ MATCHER_P(FdIsLabeled, expected_label, std::string("fd is labeled with ") + expe
91109
*result_listener << "invalid fd";
92110
return false;
93111
}
94-
char label[256] = {};
95-
ssize_t len = fgetxattr(arg, "security.selinux", label, sizeof(label));
96-
if (len < 0) {
97-
*result_listener << "fgetxattr failed with error: " << strerror(errno);
98-
return false;
112+
::testing::Matcher<fit::result<int, std::string>> expected =
113+
IsOkMatcherP<std::string>(expected_label);
114+
return expected.MatchAndExplain(GetLabel(arg), result_listener);
115+
}
116+
117+
namespace fit {
118+
119+
/// Kludge to tell gTest how to stringify `fit::result<>` values.
120+
template <typename E, typename T>
121+
void PrintTo(const fit::result<E, T>& result, std::ostream* os) {
122+
if (result.is_error()) {
123+
*os << "fit::failed( " << result.error_value() << " )";
124+
} else {
125+
*os << "fit::ok( " << result.value() << " )";
99126
}
100-
// TODO: https://fxbug.dev/395625171 - This ignores the final '\0' added by Linux.
101-
return ExplainMatchResult(testing::Eq(expected_label),
102-
std::string(std::string(label, len).c_str()), result_listener);
103127
}
104128

129+
} // namespace fit
130+
105131
#endif // SRC_STARNIX_TESTS_SELINUX_USERSPACE_UTIL_H_

0 commit comments

Comments
 (0)