Skip to content

Commit

Permalink
Add support for symlinks for regular files
Browse files Browse the repository at this point in the history
- added support for system calls: link, linkat, symlink & symlinkat
- added set_link() operation to libos_d_ops
- implemented set_link() for chroot/encrypted & tmpfs filesystems
- implemented follow_link() for chroot/encrypted & tmpfs
- added symlink support to the chroot/encrypted filesystem
- added hardlink and symlink support to tmp filesystem
- added regression unit tests for link and symlinks
- enabled symlink LTP tests
- enabled the LTP tests blocked by lack of symlink support
- updated the copyright in the modified source files
- added symlink information to the documentation

Signed-off-by: Bobby Marinov <Bobby.Marinov@fortanix.com>
  • Loading branch information
BobbyAtFortanix committed Feb 15, 2024
1 parent c06a4ae commit a441f6a
Show file tree
Hide file tree
Showing 59 changed files with 1,416 additions and 182 deletions.
32 changes: 19 additions & 13 deletions Documentation/devel/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,13 +419,13 @@ The below list is generated from the [syscall table of Linux
-`creat()`
<sup>[9a](#file-system-operations)</sup>

- `link()`
- `link()`
<sup>[9d](#hard-links-and-soft-links-symbolic-links)</sup>

-`unlink()`
<sup>[9a](#file-system-operations)</sup>

- `symlink()`
- `symlink()`
<sup>[9d](#hard-links-and-soft-links-symbolic-links)</sup>

-`readlink()`
Expand Down Expand Up @@ -966,10 +966,10 @@ The below list is generated from the [syscall table of Linux
-`renameat()`
<sup>[9a](#file-system-operations)</sup>

- `linkat()`
- `linkat()`
<sup>[9d](#hard-links-and-soft-links-symbolic-links)</sup>

- `symlinkat()`
- `symlinkat()`
<sup>[9d](#hard-links-and-soft-links-symbolic-links)</sup>

-`readlinkat()`
Expand Down Expand Up @@ -2067,6 +2067,9 @@ Gramine supports creating files and directories (via `creat()`, `mkdir()`, `mkdi
calls), reading directories (via `getdents()`), deleting files and directories (via `unlink()`,
`unlinkat()`, `rmdir()`), renaming files and directories (via `rename()` and `renameat()`).

Gramine supports creating symbolic links for files and directories (via `link()`, `linkat()`,
`symlink()` and `symlinkat()` system calls).

Gramine supports read and write operations on files. Appending to files is currently unsupported.
Writing to trusted files is prohibited.

Expand Down Expand Up @@ -2293,21 +2296,24 @@ There are two notions that must be discussed separately:

1. Host OS's links: Gramine sees them as normal files. On Linux host, these links are currently
always followed during directory/file lookup.
2. In-Gramine links: Gramine has no support for links (i.e., applications cannot create links).
- There is one exception: some pseudo-files like `/proc/[pid]/cwd` and `/proc/self`.
2. In-Gramine links: Gramine has no support for symlinks (i.e., applications can create links).
- The `chroot/encrypted` symlinks are passthrough symlinks that are stored on the host filesystem
encrypted. The `tmpfs` symlinks are stored inside the enclave memory and are not visible from
the outside.
- Hard links are only implemented for `tmpfs`. They are stored inside the enclave memory and
are not visible from the outside.

The above means that Gramine does not implement `link()` and `symlink()` system calls. Support for
`readlink()` system call is limited to only pseudo-files' links mentioned above.
Support for `readlink()` system call is limited to only pseudo-files' links mentioned above.

Gramine may implement hard and soft links in the future.
Gramine may implement a complete support for hard and soft links in the future.

<details><summary>Related system calls</summary>

- `link()`
- `symlink()`
- `link()`: see note above
- `symlink()`
-`readlink()`: see note above
- `linkat()`
- `symlinkat()`
- `linkat()`
- `symlinkat()`
-`readlinkat()`: see note above
-`lchown()`

Expand Down
6 changes: 3 additions & 3 deletions Documentation/devel/new-syscall.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ call.
4. Export new PAL calls from PAL binaries (optional)
----------------------------------------------------

For each directory in :file:`PAL/host/`, there is a :file:`pal.map` file. This
file lists all the symbols accessible to the library OS. The new PAL call needs
to be listed here in order to be used by your system call implementation.
The :file:`pal/src/pal_symbols` file lists all the symbols accessible to the library
OS. The new PAL call needs to be listed here in order to be used by your system
call implementation.

5. Implement new PAL calls (optional)
-------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions common/include/pal_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ typedef enum _pal_error_t {
PAL_ERROR_CONNFAILED,
PAL_ERROR_ADDRNOTEXIST,
PAL_ERROR_AFNOSUPPORT,
PAL_ERROR_LOOP,
PAL_ERROR_NO_PERMISSION,
PAL_ERROR_CONNFAILED_PIPE,

#define PAL_ERROR_NATIVE_COUNT PAL_ERROR_CONNFAILED_PIPE
Expand Down
2 changes: 2 additions & 0 deletions common/src/pal_error.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ static const char* g_pal_error_list[] = {
[PAL_ERROR_CONNFAILED] = "Connection failed (PAL_ERROR_CONNFAILED)",
[PAL_ERROR_ADDRNOTEXIST] = "Resource address does not exist (PAL_ERROR_ADDRNOTEXIST)",
[PAL_ERROR_AFNOSUPPORT] = "Address family not supported by protocol (PAL_ERROR_AFNOSUPPORT)",
[PAL_ERROR_LOOP] = "Symbolic link path with O_NOFOLLOW or too many symbolic links encountered (PAL_ERROR_LOOP)",
[PAL_ERROR_NO_PERMISSION] = "Operation not permitted (PAL_ERROR_NO_PERMISSION)",
[PAL_ERROR_CONNFAILED_PIPE] = "Broken pipe (PAL_ERROR_CONNFAILED_PIPE)",

[PAL_ERROR_CRYPTO_FEATURE_UNAVAILABLE] = "[Crypto] Feature not available (PAL_ERROR_CRYPTO_FEATURE_UNAVAILABLE)",
Expand Down
13 changes: 13 additions & 0 deletions libos/include/libos_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,19 @@ struct libos_d_ops {
*/
int (*follow_link)(struct libos_dentry* dent, char** out_target);

/*
* \brief Set up a link/symlink name to a dentry.
*
* \param dent Dentry (must be positive for hard links).
* \param target Link name.
* \param is_soft_link true if it is a soft/ymbolic link
*
* Set up hard/soft link name to a dentry.
*
* The caller should hold `g_dcache_lock`.
*/
int (*set_link)(struct libos_dentry* dent, const char* link, bool is_soft_link);

/*
* \brief Change file permissions.
*
Expand Down
5 changes: 5 additions & 0 deletions libos/include/libos_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ long libos_syscall_rename(const char* oldname, const char* newname);
long libos_syscall_mkdir(const char* pathname, int mode);
long libos_syscall_rmdir(const char* pathname);
long libos_syscall_creat(const char* path, mode_t mode);
long libos_syscall_link(const char* target, const char* linkpath);
long libos_syscall_symlink(const char* target, const char* linkpath);
long libos_syscall_unlink(const char* file);
long libos_syscall_readlink(const char* file, char* buf, int bufsize);
long libos_syscall_chmod(const char* filename, mode_t mode);
Expand Down Expand Up @@ -176,6 +178,9 @@ long libos_syscall_mkdirat(int dfd, const char* pathname, int mode);
long libos_syscall_newfstatat(int dirfd, const char* pathname, struct stat* statbuf, int flags);
long libos_syscall_unlinkat(int dfd, const char* pathname, int flag);
long libos_syscall_readlinkat(int dirfd, const char* file, char* buf, int bufsize);
long libos_syscall_linkat(int olddirfd, const char* target, int newdirfd, const char* linkpath,
int flags);
long libos_syscall_symlinkat(const char* target, int newdirfd, const char* linkpath);
long libos_syscall_renameat(int olddfd, const char* pathname, int newdfd, const char* newname);
long libos_syscall_fchmodat(int dfd, const char* filename, mode_t mode);
long libos_syscall_fchownat(int dfd, const char* filename, uid_t user, gid_t group, int flags);
Expand Down
8 changes: 4 additions & 4 deletions libos/src/arch/x86_64/libos_table.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ libos_syscall_t libos_syscall_table[LIBOS_SYSCALL_BOUND] = {
[__NR_mkdir] = (libos_syscall_t)libos_syscall_mkdir,
[__NR_rmdir] = (libos_syscall_t)libos_syscall_rmdir,
[__NR_creat] = (libos_syscall_t)libos_syscall_creat,
[__NR_link] = (libos_syscall_t)0, // libos_syscall_link
[__NR_link] = (libos_syscall_t)libos_syscall_link,
[__NR_unlink] = (libos_syscall_t)libos_syscall_unlink,
[__NR_symlink] = (libos_syscall_t)0, // libos_syscall_symlink
[__NR_symlink] = (libos_syscall_t)libos_syscall_symlink,
[__NR_readlink] = (libos_syscall_t)libos_syscall_readlink,
[__NR_chmod] = (libos_syscall_t)libos_syscall_chmod,
[__NR_fchmod] = (libos_syscall_t)libos_syscall_fchmod,
Expand Down Expand Up @@ -279,8 +279,8 @@ libos_syscall_t libos_syscall_table[LIBOS_SYSCALL_BOUND] = {
[__NR_newfstatat] = (libos_syscall_t)libos_syscall_newfstatat,
[__NR_unlinkat] = (libos_syscall_t)libos_syscall_unlinkat,
[__NR_renameat] = (libos_syscall_t)libos_syscall_renameat,
[__NR_linkat] = (libos_syscall_t)0, // libos_syscall_linkat
[__NR_symlinkat] = (libos_syscall_t)0, // libos_syscall_symlinkat
[__NR_linkat] = (libos_syscall_t)libos_syscall_linkat,
[__NR_symlinkat] = (libos_syscall_t)libos_syscall_symlinkat,
[__NR_readlinkat] = (libos_syscall_t)libos_syscall_readlinkat,
[__NR_fchmodat] = (libos_syscall_t)libos_syscall_fchmodat,
[__NR_faccessat] = (libos_syscall_t)libos_syscall_faccessat,
Expand Down
153 changes: 150 additions & 3 deletions libos/src/fs/chroot/encrypted.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2022 Intel Corporation
* Copyright (C) 2024 Fortanix, Inc.
* Paweł Marczewski <pawel@invisiblethingslab.com>
* Bobby Marinov <bobby.marinov@fortanix.com>
*/

/*
Expand Down Expand Up @@ -40,6 +42,8 @@
#include "stat.h"
#include "toml_utils.h"

#define USEC_IN_SEC 1000000

/*
* Always add read and write permissions to files created on host. PAL requires opening the file
* even for operations such as `unlink` or `chmod`, and the underlying `libos_fs_encrypted` module
Expand Down Expand Up @@ -258,7 +262,7 @@ static int chroot_encrypted_mkdir(struct libos_dentry* dent, mode_t perm) {
/* This opens a "dir:..." URI */
PAL_HANDLE palhdl;
ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, HOST_PERM(perm), PAL_CREATE_ALWAYS,
PAL_OPTION_PASSTHROUGH, &palhdl);
PAL_OPTION_PASSTHROUGH, false, &palhdl);
if (ret < 0) {
ret = pal_to_unix_errno(ret);
goto out;
Expand Down Expand Up @@ -290,7 +294,7 @@ static int chroot_encrypted_unlink(struct libos_dentry* dent) {

PAL_HANDLE palhdl;
ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER,
PAL_OPTION_PASSTHROUGH, &palhdl);
PAL_OPTION_PASSTHROUGH, false, &palhdl);
if (ret < 0) {
ret = pal_to_unix_errno(ret);
goto out;
Expand All @@ -308,6 +312,147 @@ static int chroot_encrypted_unlink(struct libos_dentry* dent) {
return ret;
}

static int chroot_encrypted_create_symlink_file(struct libos_dentry* link_dent,
const char* targetpath) {
assert(locked(&g_dcache_lock));
assert(link_dent->mount != NULL);

if (link_dent->inode != NULL)
return -EEXIST;

uint64_t time_us;
if (PalSystemTimeQuery(&time_us) < 0)
return -EPERM;

bool do_unlock = false;
char* uri;
int ret = chroot_dentry_uri(link_dent, S_IFREG, &uri);
if (ret < 0)
return ret;

mode_t perm = 0755;
if ((link_dent->parent != NULL) && (link_dent->parent->inode != NULL))
perm = link_dent->parent->inode->perm;

struct libos_encrypted_file* enc = NULL;
struct libos_inode* inode = get_new_inode(link_dent->mount, S_IFLNK, HOST_PERM(perm));
if (inode == NULL) {
ret = -ENOMEM;
goto out;
}

lock(&inode->lock);
do_unlock = true;

struct libos_encrypted_files_key* key = link_dent->mount->data;
ret = encrypted_file_create(uri, HOST_PERM(perm), key, &enc);
if (ret < 0)
goto out;
inode->type = S_IFLNK;

inode->data = enc;
link_dent->inode = inode;
get_inode(inode);

file_off_t pos = 0;
size_t out_count = 0;
size_t target_len = strlen(targetpath);
ret = encrypted_file_write(enc, targetpath, target_len, pos, &out_count);
if (ret < 0)
goto out;
assert(out_count <= target_len);

inode->size = target_len;
inode->mtime = time_us / USEC_IN_SEC;

out:
if (enc != NULL)
encrypted_file_put(enc);
if (inode != NULL) {
if (do_unlock)
unlock(&inode->lock);
put_inode(inode);
}
free(uri);
return ret;
}

static int chroot_encrypted_set_link(struct libos_dentry* link_dent, const char* targetpath,
bool is_soft_link) {
assert(locked(&g_dcache_lock));

int ret;
if (is_soft_link)
ret = chroot_encrypted_create_symlink_file(link_dent, targetpath);
else
ret = -EPERM;
if (ret < 0)
goto out;
ret = 0;

out:
return ret;
}

static int chroot_encrypted_follow_symlink(struct libos_dentry* link_dent, char** out_target) {
assert(locked(&g_dcache_lock));

if (link_dent->inode == NULL)
return -ENOENT;
struct libos_inode* inode = link_dent->inode;
bool put_required = false;
char* targetpath = NULL;
int ret = 0;

lock(&inode->lock);

/* open file, if not opened yet */
struct libos_encrypted_file* enc = inode->data;
ret = encrypted_file_get(enc);
if (ret < 0)
goto out;
put_required = true;

file_off_t file_sz = 0;
ret = encrypted_file_get_size(enc, &file_sz);
if (ret < 0)
goto out;
if (file_sz > PATH_MAX) {
ret = -EPERM;
goto out;
}

targetpath = malloc(file_sz + 1);
if (targetpath == NULL) {
ret = -ENOMEM;
goto out;
}

size_t out_count = 0;
ret = encrypted_file_read(enc, targetpath, file_sz, 0, &out_count);
if (ret < 0)
goto out;
static_assert(sizeof(out_count) >= sizeof(file_sz));
assert(out_count <= (size_t)file_sz);
*(targetpath + out_count) = '\x00';

*out_target = targetpath;
targetpath = NULL; /* to skip freeing it below */

out:
if (put_required)
encrypted_file_put(enc);
unlock(&inode->lock);
free(targetpath);
return ret;
}

static int chroot_encrypted_follow_link(struct libos_dentry* link_dent, char** out_target) {
assert(locked(&g_dcache_lock));

return chroot_encrypted_follow_symlink(link_dent, out_target);
}

static int chroot_encrypted_rename(struct libos_dentry* old, struct libos_dentry* new) {
assert(locked(&g_dcache_lock));
assert(old->inode);
Expand Down Expand Up @@ -348,7 +493,7 @@ static int chroot_encrypted_chmod(struct libos_dentry* dent, mode_t perm) {

PAL_HANDLE palhdl;
ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER,
PAL_OPTION_PASSTHROUGH, &palhdl);
PAL_OPTION_PASSTHROUGH, false, &palhdl);
if (ret < 0) {
ret = pal_to_unix_errno(ret);
goto out;
Expand Down Expand Up @@ -518,6 +663,8 @@ struct libos_d_ops chroot_encrypted_d_ops = {
.stat = &generic_inode_stat,
.readdir = &chroot_readdir, /* same as in `chroot` filesystem */
.unlink = &chroot_encrypted_unlink,
.follow_link = &chroot_encrypted_follow_link,
.set_link = &chroot_encrypted_set_link,
.rename = &chroot_encrypted_rename,
.chmod = &chroot_encrypted_chmod,
.idrop = &chroot_encrypted_idrop,
Expand Down
Loading

0 comments on commit a441f6a

Please sign in to comment.