From 11d8d1c88ee063e1dd9a7326ebe92106d2c7588e Mon Sep 17 00:00:00 2001 From: Jed Davis <jld@mozilla.com> Date: Thu, 24 Aug 2017 15:02:48 -0600 Subject: [PATCH] Backed out 3 changesets (bug 1380701, bug 1384804) Backed out changeset afdd35ed8902 (bug 1384804) Backed out changeset 9fb892c41a9e (bug 1380701) Backed out changeset 0d56979a6efa (bug 1380701) --- .../sandbox/linux/SandboxBrokerClient.cpp | 62 +++++++-- security/sandbox/linux/SandboxBrokerClient.h | 4 + security/sandbox/linux/SandboxFilter.cpp | 33 +++++ .../sandbox/linux/broker/SandboxBroker.cpp | 130 ++++++++++++++---- .../linux/broker/SandboxBrokerCommon.h | 3 + .../broker/SandboxBrokerPolicyFactory.cpp | 9 +- security/sandbox/linux/gtest/TestBroker.cpp | 66 ++++++++- 7 files changed, 257 insertions(+), 50 deletions(-) diff --git a/security/sandbox/linux/SandboxBrokerClient.cpp b/security/sandbox/linux/SandboxBrokerClient.cpp index b199f0873995..9adac30a5177 100644 --- a/security/sandbox/linux/SandboxBrokerClient.cpp +++ b/security/sandbox/linux/SandboxBrokerClient.cpp @@ -35,7 +35,8 @@ SandboxBrokerClient::~SandboxBrokerClient() int SandboxBrokerClient::DoCall(const Request* aReq, const char* aPath, - void* aResponseBuff, bool expectFd) + const char* aPath2, void* aResponseBuff, + bool expectFd) { // Remap /proc/self to the actual pid, so that the broker can open // it. This happens here instead of in the broker to follow the @@ -63,28 +64,38 @@ SandboxBrokerClient::DoCall(const Request* aReq, const char* aPath, } } - struct iovec ios[2]; + struct iovec ios[3]; int respFds[2]; // Set up iovecs for request + path. ios[0].iov_base = const_cast<Request*>(aReq); ios[0].iov_len = sizeof(*aReq); ios[1].iov_base = const_cast<char*>(path); - ios[1].iov_len = strlen(path); + ios[1].iov_len = strlen(path) + 1; + if (aPath2 != nullptr) { + ios[2].iov_base = const_cast<char*>(aPath2); + ios[2].iov_len = strlen(aPath2) + 1; + } else { + ios[2].iov_base = nullptr; + ios[2].iov_len = 0; + } if (ios[1].iov_len > kMaxPathLen) { return -ENAMETOOLONG; } + if (ios[2].iov_len > kMaxPathLen) { + return -ENAMETOOLONG; + } // Create response socket and send request. if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, respFds) < 0) { return -errno; } - const ssize_t sent = SendWithFd(mFileDesc, ios, 2, respFds[1]); + const ssize_t sent = SendWithFd(mFileDesc, ios, 3, respFds[1]); const int sendErrno = errno; MOZ_ASSERT(sent < 0 || static_cast<size_t>(sent) == ios[0].iov_len - + ios[1].iov_len); - + + ios[1].iov_len + + ios[2].iov_len); close(respFds[1]); if (sent < 0) { close(respFds[0]); @@ -145,7 +156,7 @@ int SandboxBrokerClient::Open(const char* aPath, int aFlags) { Request req = { SANDBOX_FILE_OPEN, aFlags, 0 }; - int maybeFd = DoCall(&req, aPath, nullptr, true); + int maybeFd = DoCall(&req, aPath, nullptr, nullptr, true); if (maybeFd >= 0) { // NSPR has opinions about file flags. Fix O_CLOEXEC. if ((aFlags & O_CLOEXEC) == 0) { @@ -159,56 +170,77 @@ int SandboxBrokerClient::Access(const char* aPath, int aMode) { Request req = { SANDBOX_FILE_ACCESS, aMode, 0 }; - return DoCall(&req, aPath, nullptr, false); + return DoCall(&req, aPath, nullptr, nullptr, false); } int SandboxBrokerClient::Stat(const char* aPath, statstruct* aStat) { Request req = { SANDBOX_FILE_STAT, 0, sizeof(statstruct) }; - return DoCall(&req, aPath, (void*)aStat, false); + return DoCall(&req, aPath, nullptr, (void*)aStat, false); } int SandboxBrokerClient::LStat(const char* aPath, statstruct* aStat) { Request req = { SANDBOX_FILE_STAT, O_NOFOLLOW, sizeof(statstruct) }; - return DoCall(&req, aPath, (void*)aStat, false); + return DoCall(&req, aPath, nullptr, (void*)aStat, false); } int SandboxBrokerClient::Chmod(const char* aPath, int aMode) { Request req = {SANDBOX_FILE_CHMOD, aMode, 0}; - return DoCall(&req, aPath, nullptr, false); + return DoCall(&req, aPath, nullptr, nullptr, false); +} + +int +SandboxBrokerClient::Link(const char* aOldPath, const char* aNewPath) +{ + Request req = {SANDBOX_FILE_LINK, 0, 0}; + return DoCall(&req, aOldPath, aNewPath, nullptr, false); +} + +int +SandboxBrokerClient::Symlink(const char* aOldPath, const char* aNewPath) +{ + Request req = {SANDBOX_FILE_SYMLINK, 0, 0}; + return DoCall(&req, aOldPath, aNewPath, nullptr, false); +} + +int +SandboxBrokerClient::Rename(const char* aOldPath, const char* aNewPath) +{ + Request req = {SANDBOX_FILE_RENAME, 0, 0}; + return DoCall(&req, aOldPath, aNewPath, nullptr, false); } int SandboxBrokerClient::Mkdir(const char* aPath, int aMode) { Request req = {SANDBOX_FILE_MKDIR, aMode, 0}; - return DoCall(&req, aPath, nullptr, false); + return DoCall(&req, aPath, nullptr, nullptr, false); } int SandboxBrokerClient::Unlink(const char* aPath) { Request req = {SANDBOX_FILE_UNLINK, 0, 0}; - return DoCall(&req, aPath, nullptr, false); + return DoCall(&req, aPath, nullptr, nullptr, false); } int SandboxBrokerClient::Rmdir(const char* aPath) { Request req = {SANDBOX_FILE_RMDIR, 0, 0}; - return DoCall(&req, aPath, nullptr, false); + return DoCall(&req, aPath, nullptr, nullptr, false); } int SandboxBrokerClient::Readlink(const char* aPath, void* aBuff, size_t aSize) { Request req = {SANDBOX_FILE_READLINK, 0, aSize}; - return DoCall(&req, aPath, aBuff, false); + return DoCall(&req, aPath, nullptr, aBuff, false); } } // namespace mozilla diff --git a/security/sandbox/linux/SandboxBrokerClient.h b/security/sandbox/linux/SandboxBrokerClient.h index 4a450b7fb0ce..06db2f1831e2 100644 --- a/security/sandbox/linux/SandboxBrokerClient.h +++ b/security/sandbox/linux/SandboxBrokerClient.h @@ -35,7 +35,10 @@ class SandboxBrokerClient final : private SandboxBrokerCommon { int Stat(const char* aPath, statstruct* aStat); int LStat(const char* aPath, statstruct* aStat); int Chmod(const char* aPath, int aMode); + int Link(const char* aPath, const char* aPath2); int Mkdir(const char* aPath, int aMode); + int Symlink(const char* aOldPath, const char* aNewPath); + int Rename(const char* aOldPath, const char* aNewPath); int Unlink(const char* aPath); int Rmdir(const char* aPath); int Readlink(const char* aPath, void* aBuf, size_t aBufSize); @@ -45,6 +48,7 @@ class SandboxBrokerClient final : private SandboxBrokerCommon { int DoCall(const Request* aReq, const char* aPath, + const char* aPath2, void *aReponseBuff, bool expectFd); }; diff --git a/security/sandbox/linux/SandboxFilter.cpp b/security/sandbox/linux/SandboxFilter.cpp index f13cfc601734..7ee7362d74b5 100644 --- a/security/sandbox/linux/SandboxFilter.cpp +++ b/security/sandbox/linux/SandboxFilter.cpp @@ -446,6 +446,27 @@ private: return broker->Chmod(path, mode); } + static intptr_t LinkTrap(ArgsRef aArgs, void *aux) { + auto broker = static_cast<SandboxBrokerClient*>(aux); + auto path = reinterpret_cast<const char*>(aArgs.args[0]); + auto path2 = reinterpret_cast<const char*>(aArgs.args[1]); + return broker->Link(path, path2); + } + + static intptr_t SymlinkTrap(ArgsRef aArgs, void *aux) { + auto broker = static_cast<SandboxBrokerClient*>(aux); + auto path = reinterpret_cast<const char*>(aArgs.args[0]); + auto path2 = reinterpret_cast<const char*>(aArgs.args[1]); + return broker->Symlink(path, path2); + } + + static intptr_t RenameTrap(ArgsRef aArgs, void *aux) { + auto broker = static_cast<SandboxBrokerClient*>(aux); + auto path = reinterpret_cast<const char*>(aArgs.args[0]); + auto path2 = reinterpret_cast<const char*>(aArgs.args[1]); + return broker->Rename(path, path2); + } + static intptr_t MkdirTrap(ArgsRef aArgs, void* aux) { auto broker = static_cast<SandboxBrokerClient*>(aux); auto path = reinterpret_cast<const char*>(aArgs.args[0]); @@ -594,8 +615,14 @@ public: return Trap(StatAtTrap, mBroker); case __NR_chmod: return Trap(ChmodTrap, mBroker); + case __NR_link: + return Trap(LinkTrap, mBroker); case __NR_mkdir: return Trap(MkdirTrap, mBroker); + case __NR_symlink: + return Trap(SymlinkTrap, mBroker); + case __NR_rename: + return Trap(RenameTrap, mBroker); case __NR_rmdir: return Trap(RmdirTrap, mBroker); case __NR_unlink: @@ -614,7 +641,10 @@ public: CASES_FOR_lstat: CASES_FOR_fstatat: case __NR_chmod: + case __NR_link: case __NR_mkdir: + case __NR_symlink: + case __NR_rename: case __NR_rmdir: case __NR_unlink: case __NR_readlink: @@ -811,6 +841,9 @@ public: case __NR_fallocate: return Allow(); + case __NR_get_mempolicy: + return Allow(); + #endif // DESKTOP #ifdef __NR_getrandom diff --git a/security/sandbox/linux/broker/SandboxBroker.cpp b/security/sandbox/linux/broker/SandboxBroker.cpp index aa8a263e4899..f693708ddca7 100644 --- a/security/sandbox/linux/broker/SandboxBroker.cpp +++ b/security/sandbox/linux/broker/SandboxBroker.cpp @@ -486,6 +486,19 @@ DoStat(const char* aPath, void* aBuff, int aFlags) return statsyscall(aPath, (statstruct*)aBuff); } +static int +DoLink(const char* aPath, const char* aPath2, + SandboxBrokerCommon::Operation aOper) +{ + if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) { + return link(aPath, aPath2); + } + if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) { + return symlink(aPath, aPath2); + } + MOZ_CRASH("SandboxBroker: Unknown link operation"); +} + size_t SandboxBroker::ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen) { @@ -574,7 +587,12 @@ SandboxBroker::ThreadMain(void) while (true) { struct iovec ios[2]; - char recvBuf[kMaxPathLen + 1]; + // We will receive the path strings in 1 buffer and split them back up. + char recvBuf[2 * (kMaxPathLen + 1)]; + char pathBuf[kMaxPathLen + 1]; + char pathBuf2[kMaxPathLen + 1]; + size_t pathLen = 0; + size_t pathLen2 = 0; char respBuf[kMaxPathLen + 1]; // Also serves as struct stat Request req; Response resp; @@ -583,10 +601,13 @@ SandboxBroker::ThreadMain(void) // Make sure stat responses fit in the response buffer MOZ_ASSERT((kMaxPathLen + 1) > sizeof(struct stat)); + // This makes our string handling below a bit less error prone. + memset(recvBuf, 0, sizeof(recvBuf)); + ios[0].iov_base = &req; ios[0].iov_len = sizeof(req); ios[1].iov_base = recvBuf; - ios[1].iov_len = sizeof(recvBuf) - 1; + ios[1].iov_len = sizeof(recvBuf); const ssize_t recvd = RecvWithFd(mFileDesc, ios, 2, &respfd); if (recvd == 0) { @@ -618,6 +639,7 @@ SandboxBroker::ThreadMain(void) // Initialize the response with the default failure. memset(&resp, 0, sizeof(resp)); + memset(&respBuf, 0, sizeof(respBuf)); resp.mError = -EACCES; ios[0].iov_base = &resp; ios[0].iov_len = sizeof(resp); @@ -625,28 +647,62 @@ SandboxBroker::ThreadMain(void) ios[1].iov_len = 0; int openedFd = -1; - size_t origPathLen = static_cast<size_t>(recvd) - sizeof(req); - // Null-terminate to get a C-style string. - MOZ_RELEASE_ASSERT(origPathLen < sizeof(recvBuf)); - recvBuf[origPathLen] = '\0'; + // Clear permissions + int perms; - // Look up the pathname but first translate relative paths. - // (Make a copy so we can get back the original path if needed.) - char pathBuf[kMaxPathLen + 1]; - base::strlcpy(pathBuf, recvBuf, sizeof(pathBuf)); - size_t pathLen = ConvertToRealPath(pathBuf, sizeof(pathBuf), origPathLen); - int perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen)); + // Find end of first string, make sure the buffer is still + // 0 terminated. + size_t recvBufLen = static_cast<size_t>(recvd) - sizeof(req); + if (recvBufLen > 0 && recvBuf[recvBufLen - 1] != 0) { + SANDBOX_LOG_ERROR("corrupted path buffer from pid %d", mChildPid); + shutdown(mFileDesc, SHUT_RD); + break; + } - // We don't have read permissions on the requested dir. - // Did we arrive from a symlink in a path that is not writable? - // Then try to figure out the original path and see if that is readable. - if (!(perms & MAY_READ)) { - // Work on the original path, - // this reverses ConvertToRealPath above. - int symlinkPerms = SymlinkPermissions(recvBuf, origPathLen); - if (symlinkPerms > 0) { - perms = symlinkPerms; + // First path should fit in maximum path length buffer. + size_t first_len = strlen(recvBuf); + if (first_len <= kMaxPathLen) { + strcpy(pathBuf, recvBuf); + // Skip right over the terminating 0, and try to copy in the + // second path, if any. If there's no path, this will hit a + // 0 immediately (we nulled the buffer before receiving). + // We do not assume the second path is 0-terminated, this is + // enforced below. + strncpy(pathBuf2, recvBuf + first_len + 1, kMaxPathLen + 1); + + // First string is guaranteed to be 0-terminated. + pathLen = first_len; + + // Look up the first pathname but first translate relative paths. + pathLen = ConvertToRealPath(pathBuf, sizeof(pathBuf), pathLen); + perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen)); + + // We don't have read permissions on the requested dir. + // Did we arrive from a symlink in a path that is not writable? + // Then try to figure out the original path and see if that is readable. + if (!(perms & MAY_READ)) { + // Work on the original path, + // this reverses ConvertToRealPath above. + int symlinkPerms = SymlinkPermissions(recvBuf, first_len); + if (symlinkPerms > 0) { + perms = symlinkPerms; + } } + + // Same for the second path. + pathLen2 = strnlen(pathBuf2, kMaxPathLen); + if (pathLen2 > 0) { + // Force 0 termination. + pathBuf2[pathLen2] = '\0'; + pathLen2 = ConvertToRealPath(pathBuf2, sizeof(pathBuf2), pathLen2); + int perms2 = mPolicy->Lookup(nsDependentCString(pathBuf2, pathLen2)); + + // Take the intersection of the permissions for both paths. + perms &= perms2; + } + } else { + // Failed to receive intelligible paths. + perms = 0; } // And now perform the operation if allowed. @@ -711,6 +767,31 @@ SandboxBroker::ThreadMain(void) } break; + case SANDBOX_FILE_LINK: + case SANDBOX_FILE_SYMLINK: + if (permissive || AllowOperation(W_OK, perms)) { + if (DoLink(pathBuf, pathBuf2, req.mOp) == 0) { + resp.mError = 0; + } else { + resp.mError = -errno; + } + } else { + AuditDenial(req.mOp, req.mFlags, perms, pathBuf); + } + break; + + case SANDBOX_FILE_RENAME: + if (permissive || AllowOperation(W_OK, perms)) { + if (rename(pathBuf, pathBuf2) == 0) { + resp.mError = 0; + } else { + resp.mError = -errno; + } + } else { + AuditDenial(req.mOp, req.mFlags, perms, pathBuf); + } + break; + case SANDBOX_FILE_MKDIR: if (permissive || AllowOperation(W_OK | X_OK, perms)) { if (mkdir(pathBuf, req.mFlags) == 0) { @@ -756,12 +837,9 @@ SandboxBroker::ThreadMain(void) case SANDBOX_FILE_READLINK: if (permissive || AllowOperation(R_OK, perms)) { - ssize_t respSize = readlink(pathBuf, (char*)&respBuf, sizeof(respBuf) - 1); + ssize_t respSize = readlink(pathBuf, (char*)&respBuf, sizeof(respBuf)); if (respSize >= 0) { - if (respSize > 0) { - // Null-terminate for nsDependentCString. - MOZ_RELEASE_ASSERT(static_cast<size_t>(respSize) < sizeof(respBuf)); - respBuf[respSize] = '\0'; + if (respSize > 0) { // Record the mapping so we can invert the file to the original // symlink. nsDependentCString orig(pathBuf, pathLen); diff --git a/security/sandbox/linux/broker/SandboxBrokerCommon.h b/security/sandbox/linux/broker/SandboxBrokerCommon.h index 6a34b2f049f8..376953907503 100644 --- a/security/sandbox/linux/broker/SandboxBrokerCommon.h +++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h @@ -30,7 +30,10 @@ public: SANDBOX_FILE_ACCESS, SANDBOX_FILE_STAT, SANDBOX_FILE_CHMOD, + SANDBOX_FILE_LINK, + SANDBOX_FILE_SYMLINK, SANDBOX_FILE_MKDIR, + SANDBOX_FILE_RENAME, SANDBOX_FILE_RMDIR, SANDBOX_FILE_UNLINK, SANDBOX_FILE_READLINK, diff --git a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp index 41d02cdf89c3..2ce04dea9168 100644 --- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp +++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp @@ -231,9 +231,7 @@ UniquePtr<SandboxBroker::Policy> SandboxBrokerPolicyFactory::GetContentPolicy(int aPid, bool aFileProcess) { // Policy entries that vary per-process (currently the only reason - // that can happen is because they contain the pid) are added here, - // as well as entries that depend on preferences or paths not available - // in early startup. + // that can happen is because they contain the pid) are added here. MOZ_ASSERT(NS_IsMainThread()); // File broker usage is controlled through a pref. @@ -274,11 +272,6 @@ SandboxBrokerPolicyFactory::GetContentPolicy(int aPid, bool aFileProcess) policy->AddPath(rdonly, nsPrintfCString("/proc/%d/statm", aPid).get()); policy->AddPath(rdonly, nsPrintfCString("/proc/%d/smaps", aPid).get()); - // Bug 1384804, notably comment 15 - // Used by libnuma, included by x265/ffmpeg, who falls back - // to get_mempolicy if this fails - policy->AddPath(rdonly, nsPrintfCString("/proc/%d/status", aPid).get()); - // userContent.css and the extensions dir sit in the profile, which is // normally blocked and we can't get the profile dir earlier in startup, // so this must happen here. diff --git a/security/sandbox/linux/gtest/TestBroker.cpp b/security/sandbox/linux/gtest/TestBroker.cpp index cec98aaeded1..e6f923026a92 100644 --- a/security/sandbox/linux/gtest/TestBroker.cpp +++ b/security/sandbox/linux/gtest/TestBroker.cpp @@ -64,9 +64,18 @@ protected: int Chmod(const char* aPath, int aMode) { return mClient->Chmod(aPath, aMode); } + int Link(const char* aPath, const char* bPath) { + return mClient->Link(aPath, bPath); + } int Mkdir(const char* aPath, int aMode) { return mClient->Mkdir(aPath, aMode); } + int Symlink(const char* aPath, const char* bPath) { + return mClient->Symlink(aPath, bPath); + } + int Rename(const char* aPath, const char* bPath) { + return mClient->Rename(aPath, bPath); + } int Rmdir(const char* aPath) { return mClient->Rmdir(aPath); } @@ -271,6 +280,43 @@ TEST_F(SandboxBrokerTest, Chmod) PrePostTestCleanup(); } +TEST_F(SandboxBrokerTest, Link) +{ + PrePostTestCleanup(); + + int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT); + ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed."; + close(fd); + ASSERT_EQ(0, Link("/tmp/blublu", "/tmp/blublublu")); + EXPECT_EQ(0, Access("/tmp/blublublu", F_OK)); + // Not whitelisted target path + EXPECT_EQ(-EACCES, Link("/tmp/blublu", "/tmp/nope")); + EXPECT_EQ(0, unlink("/tmp/blublublu")); + EXPECT_EQ(0, unlink("/tmp/blublu")); + + PrePostTestCleanup(); +} + +TEST_F(SandboxBrokerTest, Symlink) +{ + PrePostTestCleanup(); + + int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT); + ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed."; + close(fd); + ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu")); + EXPECT_EQ(0, Access("/tmp/blublublu", F_OK)); + statstruct aStat; + ASSERT_EQ(0, lstatsyscall("/tmp/blublublu", &aStat)); + EXPECT_EQ((mode_t)S_IFLNK, aStat.st_mode & S_IFMT); + // Not whitelisted target path + EXPECT_EQ(-EACCES, Symlink("/tmp/blublu", "/tmp/nope")); + EXPECT_EQ(0, unlink("/tmp/blublublu")); + EXPECT_EQ(0, unlink("/tmp/blublu")); + + PrePostTestCleanup(); +} + TEST_F(SandboxBrokerTest, Mkdir) { PrePostTestCleanup(); @@ -290,6 +336,24 @@ TEST_F(SandboxBrokerTest, Mkdir) PrePostTestCleanup(); } +TEST_F(SandboxBrokerTest, Rename) +{ + PrePostTestCleanup(); + + ASSERT_EQ(0, mkdir("/tmp/blublu", 0600)) + << "Creating dir /tmp/blublu failed."; + EXPECT_EQ(0, Access("/tmp/blublu", F_OK)); + ASSERT_EQ(0, Rename("/tmp/blublu", "/tmp/blublublu")); + EXPECT_EQ(0, Access("/tmp/blublublu", F_OK)); + EXPECT_EQ(-ENOENT , Access("/tmp/blublu", F_OK)); + // Not whitelisted target path + EXPECT_EQ(-EACCES, Rename("/tmp/blublublu", "/tmp/nope")) + << "Renaming dir without write access succeed."; + EXPECT_EQ(0, rmdir("/tmp/blublublu")); + + PrePostTestCleanup(); +} + TEST_F(SandboxBrokerTest, Rmdir) { PrePostTestCleanup(); @@ -332,7 +396,7 @@ TEST_F(SandboxBrokerTest, Readlink) int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT); ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed."; close(fd); - ASSERT_EQ(0, symlink("/tmp/blublu", "/tmp/blublublu")); + ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu")); EXPECT_EQ(0, Access("/tmp/blublublu", F_OK)); char linkBuff[256]; EXPECT_EQ(11, Readlink("/tmp/blublublu", linkBuff, sizeof(linkBuff)));