add unit test for access

Summary: This code has enough risk of a copy paste error that it deserves a unit test.

Reviewed By: chadaustin

Differential Revision: D35161787

fbshipit-source-id: 5691d13a74a0f059dfd6a93ea2852dca8399a165
This commit is contained in:
Katie Mancini 2022-04-18 21:55:15 -07:00 committed by Facebook GitHub Bot
parent fb599a7751
commit ad04eb9bc1
6 changed files with 196 additions and 60 deletions

View File

@ -43,6 +43,20 @@ target_link_libraries(
eden_nfs_rpc
)
add_library(
eden_nfs_utils STATIC
"NfsUtils.cpp" "NfsUtils.h"
)
target_link_libraries(
eden_nfs_utils
PRIVATE
eden_nfs_nfsd_rpc
Folly::folly
)
add_library(
eden_nfs_nfsd3 STATIC
"Nfsd3.cpp" "Nfsd3.h" "NfsRequestContext.cpp" "NfsRequestContext.h"
@ -55,6 +69,7 @@ target_link_libraries(
eden_nfs_rpc_server
PRIVATE
eden_nfs_nfsd_rpc
eden_nfs_utils
Folly::folly
)

54
eden/fs/nfs/NfsUtils.cpp Normal file
View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#include "eden/fs/nfs/NfsUtils.h"
#ifndef _WIN32
#include <utility>
namespace facebook::eden {
uint32_t getEffectiveAccessRights(
const struct stat& stat,
uint32_t desiredAccess) {
bool accessRead = (stat.st_mode & S_IRUSR) | (stat.st_mode & S_IRGRP) |
(stat.st_mode & S_IROTH);
bool accessWrite = (stat.st_mode & S_IWUSR) | (stat.st_mode & S_IWGRP) |
(stat.st_mode & S_IWOTH);
bool accessExecute = (stat.st_mode & S_IXUSR) | (stat.st_mode & S_IXGRP) |
(stat.st_mode & S_IXOTH);
// The delete bit indicates whether entries can be deleted from a directory
// or not. NOT whether this file can be deleted. So this bit is kinda useless
// for files. The NFS spec suggests that NFS servers should return 0 for
// files, so we only set this bit for directories.
bool accessDelete = (stat.st_mode & S_IFDIR) && accessWrite;
uint32_t expandedAccessBits = 0;
if (accessRead) {
expandedAccessBits |= ACCESS3_READ;
expandedAccessBits |= ACCESS3_LOOKUP;
}
if (accessWrite) {
expandedAccessBits |= ACCESS3_MODIFY;
expandedAccessBits |= ACCESS3_EXTEND;
}
if (accessDelete) {
expandedAccessBits |= ACCESS3_DELETE;
}
if (accessExecute) {
expandedAccessBits |= ACCESS3_EXECUTE;
}
return desiredAccess & expandedAccessBits;
}
} // namespace facebook::eden
#endif

View File

@ -162,5 +162,31 @@ inline post_op_attr statToPostOpAttr(const folly::Try<struct stat>& stat) {
}
}
/**
* Determine which of the `desiredAccess`'s a client should be granted for a
* certain file or directory based on the the `stat` of that file or directory.
* This result is an advisory result for the access call. Clients use this call
* to block IO that user's do not have access for, but procedures are still
* welcome to refuse to perform an action due to access restrictions. Thus
* this result should err on the side of being more permissive than restrictive.
*
* Really this should look at the uid & gid of the client issuing the request.
* These credentials are sent as part of the RPC credentials. This gets
* complicated because many of the authentication protocols in NFS v3 allow
* clients to spoof their uid/gid very easily. We would need to use a
* complicated authentication protocol like RPCSEC_GSS to be able to perform
* proper access checks
*
* To simplify for now, we give user's the most permissive access they could
* have as any user except root (we highly discourage acting as root inside an
* EdenFS repo). This provides a little bit of access restriction, so that
* access calls behave some what normally. However, long term we likely need to
* implement full authentication and respond properly here. We also should
* enforce permissions on each procedure call.
*/
uint32_t getEffectiveAccessRights(
const struct stat& stat,
uint32_t desiredAccess);
} // namespace facebook::eden
#endif

View File

@ -465,66 +465,6 @@ ImmediateFuture<folly::Unit> Nfsd3ServerProcessor::lookup(
});
}
/**
* Determine which of the `desiredAccess`'s a client should be granted for a
* certain file or directory based on the the `stat` of that file or directory.
* This result is an advisory result for the access call. Clients use this call
* to block IO that user's do not have access for, but procedures are still
* welcome to refuse to perform an action due to access restrictions. Thus
* this result should err on the side of being more permissive than restrictive.
*
* Really this should look at the uid & gid of the client issuing the request.
* These credentials are sent as part of the RPC credentials. This gets
* complicated because many of the authentication protocols in NFS v3 allow
* clients to spoof their uid/gid very easily. We would need to use a
* complicated authentication protocol like RPCSEC_GSS to be able to perform
* proper access checks
*
* To simplify for now, we give user's the most permissive access they could
* have as any user except root (we highly discourage acting as root inside an
* EdenFS repo). This provides a little bit of access restriction, so that
* access calls behave some what normally. However, long term we likely need to
* implement full authentication and respond properly here. We also should
* enforce permissions on each procedure call.
*/
uint32_t getEffectiveAccessRights(
const struct stat& stat,
uint32_t desiredAccess) {
bool accessRead = (stat.st_mode & S_IRUSR) | (stat.st_mode & S_IRGRP) |
(stat.st_mode & S_IROTH);
bool accessWrite = (stat.st_mode & S_IWUSR) | (stat.st_mode & S_IWGRP) |
(stat.st_mode & S_IWOTH);
bool accessExecute = (stat.st_mode & S_IXUSR) | (stat.st_mode & S_IXGRP) |
(stat.st_mode & S_IXOTH);
// The delete bit indicates whether entries can be deleted from a directory
// or not. NOT whether this file can be deleted. So this bit is kinda useless
// for files. The NFS spec suggests that NFS servers should return 0 for
// files, so we only set this bit for directories.
bool accessDelete = (stat.st_mode & S_IFDIR) && accessWrite;
uint32_t expandedAccessBits = 0;
if (accessRead) {
expandedAccessBits |= ACCESS3_READ;
expandedAccessBits |= ACCESS3_LOOKUP;
}
if (accessWrite) {
expandedAccessBits |= ACCESS3_MODIFY;
expandedAccessBits |= ACCESS3_EXTEND;
}
if (accessDelete) {
expandedAccessBits |= ACCESS3_DELETE;
}
if (accessExecute) {
expandedAccessBits |= ACCESS3_EXECUTE;
}
return desiredAccess & expandedAccessBits;
}
ImmediateFuture<folly::Unit> Nfsd3ServerProcessor::access(
folly::io::Cursor deser,
folly::io::QueueAppender ser,

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#ifndef _WIN32
#include <utility>
#include <folly/portability/GTest.h>
#include "eden/fs/nfs/NfsUtils.h"
#include "eden/fs/nfs/NfsdRpc.h"
namespace facebook::eden {
TEST(AccessTest, read) {
struct stat st {};
st.st_mode = S_IRUSR;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_READ), ACCESS3_READ);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_LOOKUP), ACCESS3_LOOKUP);
st.st_mode = S_IRGRP;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_READ), ACCESS3_READ);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_LOOKUP), ACCESS3_LOOKUP);
st.st_mode = S_IROTH;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_READ), ACCESS3_READ);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_LOOKUP), ACCESS3_LOOKUP);
st.st_mode = S_IRGRP | S_IROTH;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_READ), ACCESS3_READ);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_LOOKUP), ACCESS3_LOOKUP);
st.st_mode = S_IWGRP | S_IXOTH;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_READ), 0);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_LOOKUP), 0);
}
TEST(AccessTest, write) {
struct stat st {};
st.st_mode = S_IWUSR;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_MODIFY), ACCESS3_MODIFY);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXTEND), ACCESS3_EXTEND);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_DELETE), 0);
st.st_mode = S_IWGRP;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_MODIFY), ACCESS3_MODIFY);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXTEND), ACCESS3_EXTEND);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_DELETE), 0);
st.st_mode = S_IWOTH;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_MODIFY), ACCESS3_MODIFY);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXTEND), ACCESS3_EXTEND);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_DELETE), 0);
st.st_mode = S_IWGRP | S_IWOTH;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_MODIFY), ACCESS3_MODIFY);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXTEND), ACCESS3_EXTEND);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_DELETE), 0);
st.st_mode = S_IRUSR | S_IRGRP;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_MODIFY), 0);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXTEND), 0);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_DELETE), 0);
st.st_mode = S_IWGRP | S_IWOTH | S_IFDIR;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_MODIFY), ACCESS3_MODIFY);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXTEND), ACCESS3_EXTEND);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_DELETE), ACCESS3_DELETE);
st.st_mode = S_IRUSR | S_IRGRP | S_IFDIR;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_MODIFY), 0);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXTEND), 0);
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_DELETE), 0);
}
TEST(AccessTest, execute) {
struct stat st {};
st.st_mode = S_IXUSR;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXECUTE), ACCESS3_EXECUTE);
st.st_mode = S_IXGRP;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXECUTE), ACCESS3_EXECUTE);
st.st_mode = S_IXOTH;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXECUTE), ACCESS3_EXECUTE);
st.st_mode = S_IXGRP | S_IXOTH;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXECUTE), ACCESS3_EXECUTE);
st.st_mode = S_IRUSR | S_IWUSR;
EXPECT_EQ(getEffectiveAccessRights(st, ACCESS3_EXECUTE), 0);
}
} // namespace facebook::eden
#endif

View File

@ -14,6 +14,7 @@ target_link_libraries(
eden_nfs_test
PUBLIC
eden_nfs_nfsd_rpc
eden_nfs_utils
eden_nfs_testharness_xdr_test_utils
Folly::folly_test_util
${LIBGMOCK_LIBRARIES}