Skip to content

Commit 7a5c5e8

Browse files
Add support for symlinks for regular files
- 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 - updated the copyright in the modified source files - added symlink information to the documentation Signed-off-by: Bobby Marinov <[email protected]>
1 parent 2cef387 commit 7a5c5e8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1404
-157
lines changed

Documentation/devel/features.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -419,13 +419,13 @@ The below list is generated from the [syscall table of Linux
419419
-`creat()`
420420
<sup>[9a](#file-system-operations)</sup>
421421

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

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

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

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

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

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

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

2070+
Gramine has a limited support for creating symbolic links for files and directories
2071+
(via `link()`, `linkat()`, `symlink()` and `symlinkat()` system calls).
2072+
20702073
Gramine supports read and write operations on files. Appending to files is currently unsupported.
20712074
Writing to trusted files is prohibited.
20722075

@@ -2293,21 +2296,27 @@ There are two notions that must be discussed separately:
22932296

22942297
1. Host OS's links: Gramine sees them as normal files. On Linux host, these links are currently
22952298
always followed during directory/file lookup.
2296-
2. In-Gramine links: Gramine has no support for links (i.e., applications cannot create links).
2297-
- There is one exception: some pseudo-files like `/proc/[pid]/cwd` and `/proc/self`.
2299+
2. In-Gramine links: Gramine has a limited support for symlinks (i.e., applications can create
2300+
hard and soft links in certain file systems).
2301+
- The `chroot/encrypted` symlinks are passthrough symlinks that are stored on the host
2302+
filesystem encrypted. The `tmpfs` symlinks are stored inside the enclave memory and are
2303+
not visible from the outside.
2304+
- Hard links are only implemented for `tmpfs`. They are stored inside the enclave memory and
2305+
are not visible from the outside.
2306+
- Some pseudo-files like `/proc/[pid]/cwd` and `/proc/self` provide sym-links support foo but
2307+
those sym-links cannot be created nor destroyed.
22982308

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

2302-
Gramine may implement hard and soft links in the future.
2311+
Gramine may implement a complete support for hard and soft links in the future.
23032312

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

2306-
- `link()`
2307-
- `symlink()`
2315+
- `link()`: see note above
2316+
- `symlink()`
23082317
-`readlink()`: see note above
2309-
- `linkat()`
2310-
- `symlinkat()`
2318+
- `linkat()`
2319+
- `symlinkat()`
23112320
-`readlinkat()`: see note above
23122321
-`lchown()`
23132322

Documentation/devel/new-syscall.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ call.
5353
4. Export new PAL calls from PAL binaries (optional)
5454
----------------------------------------------------
5555

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

6060
5. Implement new PAL calls (optional)
6161
-------------------------------------

common/include/pal_error.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ typedef enum _pal_error_t {
3434
PAL_ERROR_CONNFAILED,
3535
PAL_ERROR_ADDRNOTEXIST,
3636
PAL_ERROR_AFNOSUPPORT,
37+
PAL_ERROR_LOOP,
38+
PAL_ERROR_NO_PERMISSION,
3739
PAL_ERROR_CONNFAILED_PIPE,
3840

3941
#define PAL_ERROR_NATIVE_COUNT PAL_ERROR_CONNFAILED_PIPE

common/src/pal_error.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ static const char* g_pal_error_list[] = {
2929
[PAL_ERROR_CONNFAILED] = "Connection failed (PAL_ERROR_CONNFAILED)",
3030
[PAL_ERROR_ADDRNOTEXIST] = "Resource address does not exist (PAL_ERROR_ADDRNOTEXIST)",
3131
[PAL_ERROR_AFNOSUPPORT] = "Address family not supported by protocol (PAL_ERROR_AFNOSUPPORT)",
32+
[PAL_ERROR_LOOP] = "Symbolic link path with O_NOFOLLOW or too many symbolic links encountered (PAL_ERROR_LOOP)",
33+
[PAL_ERROR_NO_PERMISSION] = "Operation not permitted (PAL_ERROR_NO_PERMISSION)",
3234
[PAL_ERROR_CONNFAILED_PIPE] = "Broken pipe (PAL_ERROR_CONNFAILED_PIPE)",
3335

3436
[PAL_ERROR_CRYPTO_FEATURE_UNAVAILABLE] = "[Crypto] Feature not available (PAL_ERROR_CRYPTO_FEATURE_UNAVAILABLE)",

libos/include/libos_fs.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,19 @@ struct libos_d_ops {
388388
*/
389389
int (*follow_link)(struct libos_dentry* dent, char** out_target);
390390

391+
/*
392+
* \brief Set up a link/symlink name to a dentry.
393+
*
394+
* \param dent Dentry (must be positive for hard links).
395+
* \param target Link name.
396+
* \param is_soft_link true if it is a soft/ymbolic link
397+
*
398+
* Set up hard/soft link name to a dentry.
399+
*
400+
* The caller should hold `g_dcache_lock`.
401+
*/
402+
int (*set_link)(struct libos_dentry* dent, const char* link, bool is_soft_link);
403+
391404
/*
392405
* \brief Change file permissions.
393406
*

libos/include/libos_table.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ long libos_syscall_rename(const char* oldname, const char* newname);
103103
long libos_syscall_mkdir(const char* pathname, int mode);
104104
long libos_syscall_rmdir(const char* pathname);
105105
long libos_syscall_creat(const char* path, mode_t mode);
106+
long libos_syscall_link(const char* target, const char* linkpath);
107+
long libos_syscall_symlink(const char* target, const char* linkpath);
106108
long libos_syscall_unlink(const char* file);
107109
long libos_syscall_readlink(const char* file, char* buf, int bufsize);
108110
long libos_syscall_chmod(const char* filename, mode_t mode);
@@ -176,6 +178,9 @@ long libos_syscall_mkdirat(int dfd, const char* pathname, int mode);
176178
long libos_syscall_newfstatat(int dirfd, const char* pathname, struct stat* statbuf, int flags);
177179
long libos_syscall_unlinkat(int dfd, const char* pathname, int flag);
178180
long libos_syscall_readlinkat(int dirfd, const char* file, char* buf, int bufsize);
181+
long libos_syscall_linkat(int olddirfd, const char* target, int newdirfd, const char* linkpath,
182+
int flags);
183+
long libos_syscall_symlinkat(const char* target, int newdirfd, const char* linkpath);
179184
long libos_syscall_renameat(int olddfd, const char* pathname, int newdfd, const char* newname);
180185
long libos_syscall_fchmodat(int dfd, const char* filename, mode_t mode);
181186
long libos_syscall_fchownat(int dfd, const char* filename, uid_t user, gid_t group, int flags);

libos/src/arch/x86_64/libos_table.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ libos_syscall_t libos_syscall_table[LIBOS_SYSCALL_BOUND] = {
100100
[__NR_mkdir] = (libos_syscall_t)libos_syscall_mkdir,
101101
[__NR_rmdir] = (libos_syscall_t)libos_syscall_rmdir,
102102
[__NR_creat] = (libos_syscall_t)libos_syscall_creat,
103-
[__NR_link] = (libos_syscall_t)0, // libos_syscall_link
103+
[__NR_link] = (libos_syscall_t)libos_syscall_link,
104104
[__NR_unlink] = (libos_syscall_t)libos_syscall_unlink,
105-
[__NR_symlink] = (libos_syscall_t)0, // libos_syscall_symlink
105+
[__NR_symlink] = (libos_syscall_t)libos_syscall_symlink,
106106
[__NR_readlink] = (libos_syscall_t)libos_syscall_readlink,
107107
[__NR_chmod] = (libos_syscall_t)libos_syscall_chmod,
108108
[__NR_fchmod] = (libos_syscall_t)libos_syscall_fchmod,
@@ -279,8 +279,8 @@ libos_syscall_t libos_syscall_table[LIBOS_SYSCALL_BOUND] = {
279279
[__NR_newfstatat] = (libos_syscall_t)libos_syscall_newfstatat,
280280
[__NR_unlinkat] = (libos_syscall_t)libos_syscall_unlinkat,
281281
[__NR_renameat] = (libos_syscall_t)libos_syscall_renameat,
282-
[__NR_linkat] = (libos_syscall_t)0, // libos_syscall_linkat
283-
[__NR_symlinkat] = (libos_syscall_t)0, // libos_syscall_symlinkat
282+
[__NR_linkat] = (libos_syscall_t)libos_syscall_linkat,
283+
[__NR_symlinkat] = (libos_syscall_t)libos_syscall_symlinkat,
284284
[__NR_readlinkat] = (libos_syscall_t)libos_syscall_readlinkat,
285285
[__NR_fchmodat] = (libos_syscall_t)libos_syscall_fchmodat,
286286
[__NR_faccessat] = (libos_syscall_t)libos_syscall_faccessat,

libos/src/fs/chroot/encrypted.c

Lines changed: 150 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* SPDX-License-Identifier: LGPL-3.0-or-later */
22
/* Copyright (C) 2022 Intel Corporation
3+
* Copyright (C) 2024 Fortanix, Inc.
34
* Paweł Marczewski <[email protected]>
5+
* Bobby Marinov <[email protected]>
46
*/
57

68
/*
@@ -40,6 +42,8 @@
4042
#include "stat.h"
4143
#include "toml_utils.h"
4244

45+
#define USEC_IN_SEC 1000000
46+
4347
/*
4448
* Always add read and write permissions to files created on host. PAL requires opening the file
4549
* even for operations such as `unlink` or `chmod`, and the underlying `libos_fs_encrypted` module
@@ -258,7 +262,7 @@ static int chroot_encrypted_mkdir(struct libos_dentry* dent, mode_t perm) {
258262
/* This opens a "dir:..." URI */
259263
PAL_HANDLE palhdl;
260264
ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, HOST_PERM(perm), PAL_CREATE_ALWAYS,
261-
PAL_OPTION_PASSTHROUGH, &palhdl);
265+
PAL_OPTION_PASSTHROUGH, false, &palhdl);
262266
if (ret < 0) {
263267
ret = pal_to_unix_errno(ret);
264268
goto out;
@@ -290,7 +294,7 @@ static int chroot_encrypted_unlink(struct libos_dentry* dent) {
290294

291295
PAL_HANDLE palhdl;
292296
ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER,
293-
PAL_OPTION_PASSTHROUGH, &palhdl);
297+
PAL_OPTION_PASSTHROUGH, false, &palhdl);
294298
if (ret < 0) {
295299
ret = pal_to_unix_errno(ret);
296300
goto out;
@@ -308,6 +312,147 @@ static int chroot_encrypted_unlink(struct libos_dentry* dent) {
308312
return ret;
309313
}
310314

315+
static int chroot_encrypted_create_symlink_file(struct libos_dentry* link_dent,
316+
const char* targetpath) {
317+
assert(locked(&g_dcache_lock));
318+
assert(link_dent->mount != NULL);
319+
320+
if (link_dent->inode != NULL)
321+
return -EEXIST;
322+
323+
uint64_t time_us;
324+
if (PalSystemTimeQuery(&time_us) < 0)
325+
return -EPERM;
326+
327+
bool do_unlock = false;
328+
char* uri;
329+
int ret = chroot_dentry_uri(link_dent, S_IFREG, &uri);
330+
if (ret < 0)
331+
return ret;
332+
333+
mode_t perm = 0755;
334+
if ((link_dent->parent != NULL) && (link_dent->parent->inode != NULL))
335+
perm = link_dent->parent->inode->perm;
336+
337+
struct libos_encrypted_file* enc = NULL;
338+
struct libos_inode* inode = get_new_inode(link_dent->mount, S_IFLNK, HOST_PERM(perm));
339+
if (inode == NULL) {
340+
ret = -ENOMEM;
341+
goto out;
342+
}
343+
344+
lock(&inode->lock);
345+
do_unlock = true;
346+
347+
struct libos_encrypted_files_key* key = link_dent->mount->data;
348+
ret = encrypted_file_create(uri, HOST_PERM(perm), key, &enc);
349+
if (ret < 0)
350+
goto out;
351+
inode->type = S_IFLNK;
352+
353+
inode->data = enc;
354+
link_dent->inode = inode;
355+
get_inode(inode);
356+
357+
file_off_t pos = 0;
358+
size_t out_count = 0;
359+
size_t target_len = strlen(targetpath);
360+
ret = encrypted_file_write(enc, targetpath, target_len, pos, &out_count);
361+
if (ret < 0)
362+
goto out;
363+
assert(out_count <= target_len);
364+
365+
inode->size = target_len;
366+
inode->mtime = time_us / USEC_IN_SEC;
367+
368+
out:
369+
if (enc != NULL)
370+
encrypted_file_put(enc);
371+
if (inode != NULL) {
372+
if (do_unlock)
373+
unlock(&inode->lock);
374+
put_inode(inode);
375+
}
376+
free(uri);
377+
return ret;
378+
}
379+
380+
static int chroot_encrypted_set_link(struct libos_dentry* link_dent, const char* targetpath,
381+
bool is_soft_link) {
382+
assert(locked(&g_dcache_lock));
383+
384+
int ret;
385+
if (is_soft_link)
386+
ret = chroot_encrypted_create_symlink_file(link_dent, targetpath);
387+
else
388+
ret = -EPERM;
389+
if (ret < 0)
390+
goto out;
391+
ret = 0;
392+
393+
out:
394+
return ret;
395+
}
396+
397+
static int chroot_encrypted_follow_symlink(struct libos_dentry* link_dent, char** out_target) {
398+
assert(locked(&g_dcache_lock));
399+
400+
if (link_dent->inode == NULL)
401+
return -ENOENT;
402+
struct libos_inode* inode = link_dent->inode;
403+
bool put_required = false;
404+
char* targetpath = NULL;
405+
int ret = 0;
406+
407+
lock(&inode->lock);
408+
409+
/* open file, if not opened yet */
410+
struct libos_encrypted_file* enc = inode->data;
411+
ret = encrypted_file_get(enc);
412+
if (ret < 0)
413+
goto out;
414+
put_required = true;
415+
416+
file_off_t file_sz = 0;
417+
ret = encrypted_file_get_size(enc, &file_sz);
418+
if (ret < 0)
419+
goto out;
420+
if (file_sz > PATH_MAX) {
421+
ret = -EPERM;
422+
goto out;
423+
}
424+
425+
targetpath = malloc(file_sz + 1);
426+
if (targetpath == NULL) {
427+
ret = -ENOMEM;
428+
goto out;
429+
}
430+
431+
size_t out_count = 0;
432+
ret = encrypted_file_read(enc, targetpath, file_sz, 0, &out_count);
433+
if (ret < 0)
434+
goto out;
435+
static_assert(sizeof(out_count) >= sizeof(file_sz));
436+
assert(out_count <= (size_t)file_sz);
437+
*(targetpath + out_count) = '\x00';
438+
439+
*out_target = targetpath;
440+
targetpath = NULL; /* to skip freeing it below */
441+
442+
out:
443+
if (put_required)
444+
encrypted_file_put(enc);
445+
unlock(&inode->lock);
446+
free(targetpath);
447+
return ret;
448+
}
449+
450+
static int chroot_encrypted_follow_link(struct libos_dentry* link_dent, char** out_target) {
451+
assert(locked(&g_dcache_lock));
452+
453+
return chroot_encrypted_follow_symlink(link_dent, out_target);
454+
}
455+
311456
static int chroot_encrypted_rename(struct libos_dentry* old, struct libos_dentry* new) {
312457
assert(locked(&g_dcache_lock));
313458
assert(old->inode);
@@ -348,7 +493,7 @@ static int chroot_encrypted_chmod(struct libos_dentry* dent, mode_t perm) {
348493

349494
PAL_HANDLE palhdl;
350495
ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER,
351-
PAL_OPTION_PASSTHROUGH, &palhdl);
496+
PAL_OPTION_PASSTHROUGH, false, &palhdl);
352497
if (ret < 0) {
353498
ret = pal_to_unix_errno(ret);
354499
goto out;
@@ -518,6 +663,8 @@ struct libos_d_ops chroot_encrypted_d_ops = {
518663
.stat = &generic_inode_stat,
519664
.readdir = &chroot_readdir, /* same as in `chroot` filesystem */
520665
.unlink = &chroot_encrypted_unlink,
666+
.follow_link = &chroot_encrypted_follow_link,
667+
.set_link = &chroot_encrypted_set_link,
521668
.rename = &chroot_encrypted_rename,
522669
.chmod = &chroot_encrypted_chmod,
523670
.idrop = &chroot_encrypted_idrop,

0 commit comments

Comments
 (0)