| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless requied by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| /* |
| * This socket tagging test is to ensure that the |
| * netfilter/xt_qtaguid kernel module somewhat behaves as expected |
| * with respect to tagging sockets. |
| */ |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <gtest/gtest.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <string> |
| |
| #define LOG_TAG "socketTagTest" |
| #include <utils/Log.h> |
| #include <testUtil.h> |
| |
| namespace android { |
| |
| class SockInfo { |
| public: |
| SockInfo() : fd(-1), addr(NULL) {}; |
| int setup(uint64_t tag); |
| bool checkTag(uint64_t tag, uid_t uid); |
| int fd; |
| void *addr; |
| }; |
| |
| |
| int openCtrl() { |
| int ctrl; |
| ctrl = open("/proc/net/xt_qtaguid/ctrl", O_RDWR); |
| if (!ctrl) { |
| testPrintE("qtaguid ctrl open failed: %s", strerror(errno)); |
| } |
| return ctrl; |
| } |
| |
| int doCtrlCommand(const char *fmt, ...) |
| __attribute__((__format__(__printf__, 1, 2))); |
| |
| int doCtrlCommand(const char *fmt, ...) { |
| char *buff; |
| int ctrl; |
| int res; |
| va_list argp; |
| |
| va_start(argp, fmt); |
| ctrl = openCtrl(); |
| vasprintf(&buff, fmt, argp); |
| errno = 0; |
| res = write(ctrl, buff, strlen(buff)); |
| testPrintI("cmd: '%s' res=%d %d/%s", buff, res, errno, strerror(errno)); |
| close(ctrl); |
| free(buff); |
| va_end(argp); |
| return res; |
| } |
| |
| |
| int writeModuleParam(const char *param, const char *data) { |
| int param_fd; |
| int res; |
| std::string filename("/sys/module/xt_qtaguid/parameters/"); |
| |
| filename += param; |
| param_fd = open(filename.c_str(), O_WRONLY); |
| if (param_fd < 0) { |
| testPrintE("qtaguid param open failed: %s", strerror(errno)); |
| return -1; |
| } |
| res = write(param_fd, data, strlen(data)); |
| if (res < 0) { |
| testPrintE("qtaguid param write failed: %s", strerror(errno)); |
| } |
| close(param_fd); |
| return res; |
| } |
| |
| /*----------------------------------------------------------------*/ |
| int SockInfo::setup(uint64_t tag) { |
| fd = socket(AF_INET, SOCK_STREAM, 0); |
| if (fd < 0) { |
| testPrintE("socket creation failed: %s", strerror(errno)); |
| return -1; |
| } |
| if (doCtrlCommand("t %d %" PRIu64, fd, tag) < 0) { |
| testPrintE("socket setup: failed to tag"); |
| close(fd); |
| return -1; |
| } |
| if (!checkTag(tag, getuid())) { |
| testPrintE("socket setup: Unexpected results: tag not found"); |
| close(fd); |
| return -1; |
| } |
| if (doCtrlCommand("u %d", fd) < 0) { |
| testPrintE("socket setup: Unexpected results"); |
| close(fd); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* checkTag() also tries to lookup the socket address in the kernel and |
| * return it when *addr == NULL. |
| * This allows for better look ups when another process is also setting the same |
| * tag + uid. But it is not fool proof. |
| * Without the kernel reporting more info on who setup the socket tag, it is |
| * not easily verifiable from user-space. |
| * Returns: true if tag found. |
| */ |
| bool SockInfo::checkTag(uint64_t acct_tag, uid_t uid) { |
| int ctrl_fd; |
| ctrl_fd = openCtrl(); |
| char ctrl_data[1024]; |
| ssize_t read_size; |
| char *buff; |
| char *pos; |
| int res; |
| char *match_template; |
| uint64_t k_tag; |
| uint32_t k_uid; |
| uint64_t full_tag; |
| long dummy_count; |
| pid_t dummy_pid; |
| |
| read_size = read(ctrl_fd, ctrl_data, sizeof(ctrl_data)); |
| if (read_size < 0) { |
| testPrintE("Unable to read active tags from ctrl %d/%s", |
| errno, strerror(errno)); |
| } |
| ctrl_data[read_size] = '\0'; |
| testPrintI("<ctrl_raw_data>\n%s</ctrl_raw_data>", ctrl_data); |
| |
| if (addr) { |
| assert(sizeof(void*) == sizeof(long int)); // Why does %p use 0x? grrr. %lx. |
| asprintf(&match_template, "sock=%" PRIxPTR " %s", (uintptr_t)addr, "tag=0x%" PRIx64" (uid=%u)"); |
| } |
| else { |
| /* Allocate for symmetry */ |
| asprintf(&match_template, "%s", " tag=0x%" PRIx64 " (uid=%u)"); |
| } |
| |
| full_tag = acct_tag | uid; |
| |
| asprintf(&buff, match_template, full_tag | uid, uid); |
| testPrintI("looking for '%s'", buff); |
| pos = strstr(ctrl_data, buff); |
| |
| if (pos && !addr) { |
| assert(sizeof(void*) == sizeof(long int)); // Why does %p use 0x? grrr. %lx. |
| res = sscanf(pos - strlen("sock=1234abcd"), |
| "sock=%" SCNxPTR " tag=0x%" SCNx64 " (uid=%" SCNu32 ") pid=%u f_count=%lu", |
| (uintptr_t *)&addr, &k_tag, &k_uid, &dummy_pid, &dummy_count ); |
| if (!(res == 5 && k_tag == full_tag && k_uid == uid)) { |
| testPrintE("Unable to read sock addr res=%d", res); |
| addr = 0; |
| } |
| else { |
| testPrintI("Got sock_addr %lx", addr); |
| } |
| } |
| free(buff); |
| free(match_template); |
| close(ctrl_fd); |
| return pos != NULL; |
| } |
| |
| |
| class SocketTaggingTest : public ::testing::Test { |
| protected: |
| virtual void SetUp() { |
| ctrl_fd = -1; |
| dev_fd = -1; |
| my_uid = getuid(); |
| my_pid = getpid(); |
| srand48(my_pid * my_uid); |
| // Adjust fake UIDs and tags so that multiple instances can run in parallel. |
| fake_uid = testRand(); |
| fake_uid2 = testRand(); |
| valid_tag1 = ((uint64_t)my_pid << 48) | ((uint64_t)testRand() << 32); |
| valid_tag2 = ((uint64_t)my_pid << 48) | ((uint64_t)testRand() << 32); |
| valid_tag2 &= 0xffffff00ffffffffllu; // Leave some room to make counts visible. |
| testPrintI("* start: pid=%lu uid=%lu uid1=0x%lx/%lu uid2=0x%lx/%lu" |
| " tag1=0x%" PRIx64 "/%" PRIu64 " tag2=0x%" PRIx64 "/% " PRIu64, |
| (unsigned long)my_pid, (unsigned long)my_uid, |
| (unsigned long)fake_uid, (unsigned long)fake_uid, |
| (unsigned long)fake_uid2, (unsigned long)fake_uid2, |
| valid_tag1, valid_tag1, valid_tag2, valid_tag2); |
| max_uint_tag = 0xffffffff00000000llu; |
| max_uint_tag = 1llu << 63 | (((uint64_t)my_pid << 48) ^ max_uint_tag); |
| |
| testPrintI("kernel has qtaguid"); |
| ctrl_fd = openCtrl(); |
| ASSERT_GE(ctrl_fd, 0) << "qtaguid ctrl open failed"; |
| close(ctrl_fd); |
| dev_fd = open("/dev/xt_qtaguid", O_RDONLY); |
| EXPECT_GE(dev_fd, 0) << "qtaguid dev open failed"; |
| |
| // We want to clean up any previous faulty test runs. |
| testPrintI("delete command does not fail"); |
| EXPECT_GE(doCtrlCommand("d 0 %u", fake_uid), 0) << "Failed to delete fake_uid"; |
| EXPECT_GE(doCtrlCommand("d 0 %u", fake_uid2), 0) << "Failed to delete fake_uid2"; |
| EXPECT_GE(doCtrlCommand("d 0 %u", my_uid), 0) << "Failed to delete my_uid"; |
| |
| testPrintI("setup sock0 and addr via tag"); |
| ASSERT_FALSE(sock0.setup(valid_tag1)) << "socket0 setup failed"; |
| testPrintI("setup sock1 and addr via tag"); |
| ASSERT_FALSE(sock1.setup(valid_tag1)) << "socket1 setup failed"; |
| } |
| |
| virtual void TearDown() { |
| if (dev_fd >= 0) { |
| close(dev_fd); |
| } |
| if (ctrl_fd >= 0) { |
| close(ctrl_fd); |
| } |
| } |
| |
| SockInfo sock0; |
| SockInfo sock1; |
| int ctrl_fd; |
| int dev_fd; |
| uid_t fake_uid; |
| uid_t fake_uid2; |
| uid_t my_uid; |
| pid_t my_pid; |
| uint64_t valid_tag1; |
| uint64_t valid_tag2; |
| uint64_t max_uint_tag; |
| static const uint64_t invalid_tag1 = 0x0000000100000001llu; |
| static const int max_tags = 5; |
| }; |
| |
| TEST_F(SocketTaggingTest, TagData) { |
| max_uint_tag = 0xffffffff00000000llu; |
| char *max_tags_str; |
| |
| testPrintI("setup tag limit"); |
| asprintf(&max_tags_str, "%d", max_tags); |
| ASSERT_GE(writeModuleParam("max_sock_tags", max_tags_str), 0) << "Failed to setup tag limit"; |
| |
| testPrintI("tag quota reach limit"); |
| for (int cnt = 0; cnt < max_tags; cnt++ ) { |
| uint64_t tag = valid_tag2 + ((uint64_t)cnt << 32); |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, tag , fake_uid2), 0) |
| << "Tagging within limit failed"; |
| EXPECT_TRUE(sock0.checkTag(tag, fake_uid2))<< "Unexpected results: tag not found"; |
| } |
| |
| testPrintI("tag quota go over limit"); |
| uint64_t new_tag = valid_tag2 + ((uint64_t)max_tags << 32); |
| EXPECT_LT(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, new_tag, fake_uid2), 0); |
| EXPECT_TRUE(sock0.checkTag(valid_tag2 + (((uint64_t)max_tags - 1) << 32), |
| fake_uid2)) << "Unexpected results: tag not found"; |
| |
| testPrintI("valid untag"); |
| EXPECT_GE(doCtrlCommand("u %d", sock0.fd), 0); |
| EXPECT_FALSE(sock0.checkTag(valid_tag2 + (((uint64_t)max_tags - 1) << 32), fake_uid2)) |
| << "Untagged tag should not be there"; |
| |
| testPrintI("tag after untag should not free up max tags"); |
| uint64_t new_tag2 = valid_tag2 + ((uint64_t)max_tags << 32); |
| EXPECT_LT(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, new_tag2 , fake_uid2), 0); |
| EXPECT_FALSE(sock0.checkTag(valid_tag2 + ((uint64_t)max_tags << 32), fake_uid2)) |
| << "Tag should not be there"; |
| |
| testPrintI("delete one tag"); |
| uint64_t new_tag3 = valid_tag2 + (((uint64_t)max_tags / 2) << 32); |
| EXPECT_GE(doCtrlCommand("d %" PRIu64 " %u", new_tag3, fake_uid2), 0); |
| |
| testPrintI("2 tags after 1 delete pass/fail"); |
| uint64_t new_tag4; |
| new_tag4 = valid_tag2 + (((uint64_t)max_tags + 1 ) << 32); |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, new_tag4 , fake_uid2), 0); |
| EXPECT_TRUE(sock0.checkTag(valid_tag2 + (((uint64_t)max_tags + 1) << 32), fake_uid2)) |
| << "Tag not found"; |
| new_tag4 = valid_tag2 + (((uint64_t)max_tags + 2 ) << 32); |
| EXPECT_LT(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, new_tag4 , fake_uid2), 0); |
| EXPECT_FALSE(sock0.checkTag(valid_tag2 + (((uint64_t)max_tags + 2) << 32), fake_uid2)) |
| << "Tag should not be there"; |
| |
| /* TODO(jpa): test tagging two different sockets with same tags and |
| * check refcounts the tag_node should be +2 |
| */ |
| } |
| |
| TEST_F(SocketTaggingTest, InsufficientArgsFails) { |
| // Insufficient args. Expected failure |
| EXPECT_LE(doCtrlCommand("t"), 0) << "Insufficient args, should fail."; |
| } |
| |
| TEST_F(SocketTaggingTest, BadCommandFails) { |
| // Bad command. Expected failure"; |
| EXPECT_LE(doCtrlCommand("?"), 0) << "Bad command, should fail"; |
| } |
| |
| TEST_F(SocketTaggingTest, NoTagNoUid) { |
| // no tag, no uid |
| EXPECT_GE(doCtrlCommand("t %d", sock0.fd), 0); |
| ASSERT_TRUE(sock0.checkTag(0, my_uid)) << "Tag not found"; |
| } |
| |
| TEST_F(SocketTaggingTest, InvalidTagFail) { |
| // Invalid tag. Expected failure |
| EXPECT_LE(doCtrlCommand("t %d %" PRIu64, sock0.fd, invalid_tag1), 0); |
| ASSERT_FALSE(sock0.checkTag(invalid_tag1, my_uid)) << "Tag should not be there"; |
| } |
| |
| TEST_F(SocketTaggingTest, ValidTagWithNoUid) { |
| // Valid tag with no uid |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64, sock0.fd, valid_tag1), 0); |
| EXPECT_TRUE(sock0.checkTag(valid_tag1, my_uid)) << "Tag not found"; |
| } |
| |
| TEST_F(SocketTaggingTest, ValidUntag) { |
| // Valid untag |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64, sock0.fd, valid_tag1), 0); |
| EXPECT_TRUE(sock0.checkTag(valid_tag1, my_uid)) << "Tag not found"; |
| EXPECT_GE(doCtrlCommand("u %d", sock0.fd), 0); |
| EXPECT_FALSE(sock0.checkTag(valid_tag1, my_uid)) << "Tag should be removed"; |
| } |
| |
| TEST_F(SocketTaggingTest, ValidFirsttag) { |
| // Valid 1st tag |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid), 0); |
| EXPECT_TRUE(sock0.checkTag(valid_tag2, fake_uid)) << "Tag not found."; |
| } |
| |
| TEST_F(SocketTaggingTest, ValidReTag) { |
| // Valid re-tag |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid), 0); |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid), 0); |
| EXPECT_TRUE(sock0.checkTag(valid_tag2, fake_uid)) << "Tag not found."; |
| } |
| |
| TEST_F(SocketTaggingTest, ValidReTagWithAcctTagChange) { |
| // Valid re-tag with acct_tag change |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid), 0); |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag1, fake_uid), 0); |
| EXPECT_TRUE(sock0.checkTag(valid_tag1, fake_uid)) << "Tag not found."; |
| } |
| |
| TEST_F(SocketTaggingTest, ReTagWithUidChange) { |
| // Re-tag with uid change |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag1, fake_uid), 0); |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid2), 0); |
| } |
| |
| TEST_F(SocketTaggingTest, Valid64BitAcctTag) { |
| // Valid 64bit acct tag |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64, sock0.fd, max_uint_tag), 0); |
| EXPECT_TRUE(sock0.checkTag(max_uint_tag, my_uid)) << "Tag not found."; |
| } |
| |
| TEST_F(SocketTaggingTest, TagAnotherSocket) { |
| testPrintI("Tag two sockets"); |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64, sock0.fd, max_uint_tag), 0); |
| EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock1.fd, valid_tag1, fake_uid2), 0); |
| EXPECT_TRUE(sock1.checkTag(valid_tag1, fake_uid2)) << "Tag not found."; |
| testPrintI("Untag socket0 of them only."); |
| EXPECT_GE(doCtrlCommand("u %d", sock0.fd), 0); |
| EXPECT_FALSE(sock0.checkTag(max_uint_tag, fake_uid)) << "Tag should not be there"; |
| EXPECT_TRUE(sock1.checkTag(valid_tag1, fake_uid2)) << "Tag not found"; |
| testPrintI("Now untag socket1 as well."); |
| EXPECT_GE(doCtrlCommand("u %d", sock1.fd), 0); |
| EXPECT_FALSE(sock1.checkTag(valid_tag1, fake_uid2)) << "Tag should not be there"; |
| } |
| |
| TEST_F(SocketTaggingTest, TagInvalidSocketFail) { |
| // Invalid tag. Expected failure |
| close(sock0.fd); |
| EXPECT_LE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag1, my_uid), 0); |
| EXPECT_FALSE(sock0.checkTag(valid_tag1, my_uid)) << "Tag should not be there"; |
| } |
| |
| TEST_F(SocketTaggingTest, UntagInvalidSocketFail) { |
| // Invalid untag. Expected failure"; |
| close(sock1.fd); |
| EXPECT_LE(doCtrlCommand("u %d", sock1.fd), 0); |
| } |
| |
| } // namespace android |