Compare commits
No commits in common. "master" and "WIP/enosys" have entirely different histories.
master
...
WIP/enosys
63
README.md
63
README.md
@ -1,12 +1,13 @@
|
|||||||
# exile.h
|
# exile.h
|
||||||
`exile.h` provides an API for processes on Linux to easily isolate themselves in order
|
`exile.h` is a header-only library, enabling processes to easily isolate themselves on Linux for exploit mitigation purposes. exile.h wants to make existing technologies, such as Seccomp and Linux Namespaces, easier to use. Those generally
|
||||||
to mitigate the effect of exploited vulnerabilities, i. e. when attacker has achieved
|
require knowledge of details and are not trivial for developers to employ, which prevents a more widespread adoption.
|
||||||
arbitrary code execution. exile.h makes it simpler for developers to use existing technologies such as Seccomp and Linux Namespaces. Those generally require knowledge of details and are not trivial for developers to employ, which prevents a more widespread adoption.
|
|
||||||
|
|
||||||
The following section offers small examples. Then the motivation is explained in more detail. Proper API documentation will be maintained in other files.
|
The following section offers small examples. Then the motivation is explained in more detail.
|
||||||
|
Proper API documentation will be maintained in other files.
|
||||||
|
|
||||||
## Quick demo
|
## Quick demo
|
||||||
This section quickly demonstrates the simplicity of the API. It serves as an overview to get a first impression.
|
This section quickly demonstrates the simplicity of the API. It serves as an overview to get
|
||||||
|
a first impression.
|
||||||
|
|
||||||
system() is used to keep the example C code short. It also demonstrates that subprocesses are also subject to restrictions imposed by exile.h.
|
system() is used to keep the example C code short. It also demonstrates that subprocesses are also subject to restrictions imposed by exile.h.
|
||||||
|
|
||||||
@ -39,12 +40,12 @@ int main(void)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The assert() calls won't be fired, consistent with the policy that allows only reading
|
The assert() calls won't be fired, consistent with the policy.
|
||||||
from /home/user. We can write to /tmp/ though as it was specified in the policy.
|
|
||||||
|
|
||||||
### vows(): pledge()-like API / System call policies
|
### System call policies / vows
|
||||||
exile.h allows specifying which syscalls are permitted or denied. In the following example,
|
exile.h allows specifying which syscalls are permitted or denied. In the following example,
|
||||||
'ls' is never executed, as the specified "vows" do not allow the execve() system call. The process will be killed.
|
ls is never executed, as the specified "vows" do not allow the execve() system call. The
|
||||||
|
process will be killed.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
#include "exile.h"
|
#include "exile.h"
|
||||||
@ -80,7 +81,7 @@ int main(void)
|
|||||||
Produces ```curl: (6) Could not resolve host: evil.tld```. For example, this is useful for subprocesses which do not need
|
Produces ```curl: (6) Could not resolve host: evil.tld```. For example, this is useful for subprocesses which do not need
|
||||||
network access, but perform tasks such as parsing user-supplied file formats.
|
network access, but perform tasks such as parsing user-supplied file formats.
|
||||||
|
|
||||||
### Isolation of single functions (EXPERIMENTAL)
|
### Isolation of single functions
|
||||||
Currently, work is being done that hopefully will allow isolation of individual function calls in a mostly pain-free manner.
|
Currently, work is being done that hopefully will allow isolation of individual function calls in a mostly pain-free manner.
|
||||||
|
|
||||||
Consider the following C++ code:
|
Consider the following C++ code:
|
||||||
@ -127,28 +128,23 @@ We execute "cat()". The first call succeeds. In the second, we get an exception,
|
|||||||
the subprocess "cat()" was launched in violated the policy (missing "rpath" vow).
|
the subprocess "cat()" was launched in violated the policy (missing "rpath" vow).
|
||||||
|
|
||||||
Naturally, there is a performance overhead. Certain challenges remain, such as the fact
|
Naturally, there is a performance overhead. Certain challenges remain, such as the fact
|
||||||
that being executed in a subproces, we operate on copies, so handling references
|
that being executed in a subprocess, we operate on copies, so handling references
|
||||||
is not something that has been given much thought. There is also the fact
|
is not something that has been given much thought. There is also the fact
|
||||||
that clone()ing from threads opens a can of worms, particularly with locks. Hence, exile_launch() is best avoided in multi-threaded contexts.
|
that clone()ing from threads opens a can of worms, particularly with locks. Hence, exile_launch()
|
||||||
|
is best avoided in multi-threaded contexts.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
No release yet, experimental, API is unstable, builds will break on updates of this library.
|
No release yet, experimental, API is unstable, builds will break on updates of this library.
|
||||||
|
|
||||||
Currently, it's mainly evolving from the needs of my other projects which use exile.h.
|
Currently, it's mainly evolving from the needs of my other projects.
|
||||||
|
|
||||||
|
|
||||||
### Real-world usage
|
|
||||||
- looqs: https://github.com/quitesimpleorg/looqs
|
|
||||||
- qswiki: https://gitea.quitesimple.org/crtxcr/qswiki
|
|
||||||
|
|
||||||
|
|
||||||
## Motivation and Background
|
## Motivation and Background
|
||||||
exile.h unlocks existing Linux mechanisms to facilitate isolation of processes from resources. Limiting the scope of what programs can do helps defending the rest of the system when a process gets under attacker's control (when classic mitigations such as ASLR etc. failed). To this end, OpenBSD has the pledge() and unveil() functions available. Those functions are helpful mitigation mechanisms, but such accessible ways are unfortunately not readily available on Linux. This is where exile.h steps in.
|
exile.h unlocks existing Linux mechanisms to facilitate isolation of processes from resources. Limiting the scope of what programs can do helps defending the rest of the system when a process gets under attacker's control (when classic mitigations such as ASLR etc. failed). To this end, OpenBSD has the pledge() and unveil() functions available. Those functions are helpful mitigation mechanisms, but such accessible ways are unfortunately not readily available on Linux. This is where exile.h steps in.
|
||||||
|
|
||||||
Seccomp allows restricting the system calls available to a process and thus decrease the systems attack surface, but it generally is not easy to use. Requiring BPF filter instructions, you generally just can't make use of it right away without learning
|
Seccomp allows restricting the system calls available to a process and thus decrease the systems attack surface, but it generally is not easy to use. Requiring BPF filter instructions, you generally just can't make use of it right away. exile.h provides an API inspired by pledge(), building on top of seccomp. It also provides an interface to manually restrict the system calls that can be issued.
|
||||||
about BPF. exile.h provides an API inspired by pledge(), building on top of seccomp. It also provides an interface to manually restrict the system calls that can be issued.
|
|
||||||
|
|
||||||
Traditional methods employed to restrict file system access, like different uids/gids, chroot, bind-mounts, namespaces etc. may require administrator intervention, are perhaps only suitable for daemons and not desktop applications, or are generally rather involved. As a positive example, Landlock since 5.13 is a vast improvement to limit file system access of processes. It also greatly simplifies exile.h' implementation of fs isolation.
|
Traditional methods employed to restrict file system access, like different uids/gids, chroot, bind-mounts, namespaces etc. may require administrator intervention, are perhaps only suitable
|
||||||
|
for daemons and not desktop applications, or are generally rather involved. As a positive example, Landlock since 5.13 is a vast improvement to limit file system access of processes. It also greatly simplifies exile.h' implementation of fs isolation.
|
||||||
|
|
||||||
Abstracting those details may help developers bring sandboxing into their applications.
|
Abstracting those details may help developers bring sandboxing into their applications.
|
||||||
|
|
||||||
@ -174,24 +170,18 @@ It's recommended to start with [README.usage.md] to get a feeling for exile.h.
|
|||||||
API-Documentation: [README.api.md]
|
API-Documentation: [README.api.md]
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
Built upon kernel technologies, exile.h naturally inherits their limitations:
|
|
||||||
|
|
||||||
- New syscalls can be introduced by new kernel versions. exile.h must keep in sync, and users must keep the library up to date.
|
|
||||||
- seccomp has no deep argument inspection (yet), particularly new syscalls
|
|
||||||
cannot be reasonably filtered, such as clone3(), or io_uring.
|
|
||||||
- You can't know what syscalls libraries will issue. An update to existing
|
|
||||||
libraries may cause them to use different syscalls not allowed by a policy. However, using vows and keeping up to date with exile.h should cover that.
|
|
||||||
- Landlock, currently, does not apply to syscalls such as stat().
|
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
|
- seccomp must be kept up to date syscalls kernel
|
||||||
- ioctl does not know the fd, so checking values is kind of strange
|
- ioctl does not know the fd, so checking values is kind of strange
|
||||||
- redundancies: some things are handled by capabilties, other by seccomp or both
|
- redundancies: some things are handled by capabilties, other by seccomp or both
|
||||||
|
- seccomp no deep argument inspection
|
||||||
|
- landlock: stat() does not apply
|
||||||
- no magic, be reasonable, devs should not get sloppy, restrict IPC.
|
- no magic, be reasonable, devs should not get sloppy, restrict IPC.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
Kernel >=3.17
|
Kernel >=3.17
|
||||||
|
|
||||||
While mostly transparent to users of this API, kernel >= 5.13 is required to take advantage of Landlock. Furthermore, it depends on distro-provided kernels being reasonable and enabling it by default. In practise, Landlock maybe won't be used in some cases so exile.h will use a combination of namespaces, bind mounts and chroot as fallbacks.
|
While mostly transparent to users of this API, kernel >= 5.13 is required to take advantage of Landlock. Furthermore, it depends on distro-provided kernels being reasonable and enabling it by default. In practise, this means that Landlock probably won't be used for now, and exile.h will use a combination of namespaces, bind mounts and chroot as fallbacks.
|
||||||
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
@ -207,8 +197,13 @@ You can thank a Debian-specific kernel patch for that. Execute
|
|||||||
|
|
||||||
Note that newer releases should not cause this problem any longer, as [explained](https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#linux-user-namespaces) in the Debian release notes.
|
Note that newer releases should not cause this problem any longer, as [explained](https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#linux-user-namespaces) in the Debian release notes.
|
||||||
|
|
||||||
### Why "vows"?
|
### Real-world usage
|
||||||
pledge() cannot be properly implemented using seccomp. The "vow" concept here may look similiar, and it is, but it's not pledge().
|
- looqs: https://gitea.quitesimple.org/crtxcr/looqs
|
||||||
|
- qswiki: https://gitea.quitesimple.org/crtxcr/qswiki
|
||||||
|
|
||||||
|
Outdated:
|
||||||
|
- cgit sandboxed: https://gitea.quitesimple.org/crtxcr/cgitsb
|
||||||
|
- qpdfviewsb sandboxed (quick and dirty): https://gitea.quitesimple.org/crtxcr/qpdfviewsb
|
||||||
|
|
||||||
### Other projects
|
### Other projects
|
||||||
- [sandbox2](https://developers.google.com/code-sandboxing/sandbox2/)
|
- [sandbox2](https://developers.google.com/code-sandboxing/sandbox2/)
|
||||||
|
249
exile.c
249
exile.c
@ -361,11 +361,10 @@ inline int exile_landlock_is_available()
|
|||||||
{
|
{
|
||||||
#if HAVE_LANDLOCK == 1
|
#if HAVE_LANDLOCK == 1
|
||||||
int ruleset = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
|
int ruleset = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
|
||||||
return ruleset > 0;
|
return ruleset == 1;
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall, unsigned int syscall_policy, struct sock_filter *argfilters, size_t n)
|
int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall, unsigned int syscall_policy, struct sock_filter *argfilters, size_t n)
|
||||||
{
|
{
|
||||||
struct exile_syscall_policy *newpolicy = (struct exile_syscall_policy *) calloc(1, sizeof(struct exile_syscall_policy));
|
struct exile_syscall_policy *newpolicy = (struct exile_syscall_policy *) calloc(1, sizeof(struct exile_syscall_policy));
|
||||||
@ -382,7 +381,6 @@ int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall,
|
|||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("Too many argfilters supplied\n");
|
EXILE_LOG_ERROR("Too many argfilters supplied\n");
|
||||||
exile_policy->exile_flags |= EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL;
|
exile_policy->exile_flags |= EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL;
|
||||||
free(newpolicy);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
for(size_t i = 0; i < n; i++)
|
for(size_t i = 0; i < n; i++)
|
||||||
@ -621,12 +619,10 @@ struct exile_policy *exile_init_policy()
|
|||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
result->drop_caps = 0;
|
result->drop_caps = 1;
|
||||||
result->not_dumpable = 1;
|
result->not_dumpable = 1;
|
||||||
result->no_new_privs = 1;
|
result->no_new_privs = 1;
|
||||||
result->namespace_options = EXILE_UNSHARE_AUTOMATIC;
|
result->namespace_options = EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_USER;
|
||||||
result->namespace_uid = 0;
|
|
||||||
result->namespace_gid = 0;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -818,13 +814,11 @@ char *concat_path(const char *first, const char *second)
|
|||||||
if(written < 0)
|
if(written < 0)
|
||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("Error during path concatination\n");
|
EXILE_LOG_ERROR("Error during path concatination\n");
|
||||||
free(result);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if(written >= PATH_MAX)
|
if(written >= PATH_MAX)
|
||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("path concatination truncated\n");
|
EXILE_LOG_ERROR("path concatination truncated\n");
|
||||||
free(result);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -875,18 +869,18 @@ static int perform_mounts(const char *chroot_target_path, struct exile_path_poli
|
|||||||
{
|
{
|
||||||
while(path_policy != NULL)
|
while(path_policy != NULL)
|
||||||
{
|
{
|
||||||
|
int mount_flags = get_policy_mount_flags(path_policy);
|
||||||
|
|
||||||
|
char *path_inside_chroot = concat_path(chroot_target_path, path_policy->path);
|
||||||
|
if(path_inside_chroot == NULL)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
//all we do is bind mounts
|
||||||
|
mount_flags |= MS_BIND;
|
||||||
|
|
||||||
if(path_policy->policy & EXILE_FS_ALLOW_ALL_READ || path_policy->policy & EXILE_FS_ALLOW_ALL_WRITE)
|
if(path_policy->policy & EXILE_FS_ALLOW_ALL_READ || path_policy->policy & EXILE_FS_ALLOW_ALL_WRITE)
|
||||||
{
|
{
|
||||||
int mount_flags = get_policy_mount_flags(path_policy);
|
|
||||||
|
|
||||||
char *path_inside_chroot = concat_path(chroot_target_path, path_policy->path);
|
|
||||||
if(path_inside_chroot == NULL)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
//all we do is bind mounts
|
|
||||||
mount_flags |= MS_BIND;
|
|
||||||
|
|
||||||
int ret = mount(path_policy->path, path_inside_chroot, NULL, mount_flags, NULL);
|
int ret = mount(path_policy->path, path_inside_chroot, NULL, mount_flags, NULL);
|
||||||
if(ret < 0 )
|
if(ret < 0 )
|
||||||
{
|
{
|
||||||
@ -903,10 +897,9 @@ static int perform_mounts(const char *chroot_target_path, struct exile_path_poli
|
|||||||
free(path_inside_chroot);
|
free(path_inside_chroot);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
path_policy = path_policy->next;
|
||||||
free(path_inside_chroot);
|
free(path_inside_chroot);
|
||||||
}
|
}
|
||||||
path_policy = path_policy->next;
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -940,15 +933,10 @@ void exile_free_policy(struct exile_policy *ctxt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Enters the specified namespaces */
|
/* Enters the specified namespaces */
|
||||||
static int enter_namespaces(int namespace_options, uid_t namespace_uid, gid_t namespace_gid)
|
static int enter_namespaces(int namespace_options)
|
||||||
{
|
{
|
||||||
if(namespace_options & EXILE_UNSHARE_USER)
|
if(namespace_options & EXILE_UNSHARE_USER)
|
||||||
{
|
{
|
||||||
uid_t current_uid = getuid();
|
|
||||||
gid_t current_gid = getgid();
|
|
||||||
|
|
||||||
char buf[1024] = {0};
|
|
||||||
|
|
||||||
int ret = unshare(CLONE_NEWUSER);
|
int ret = unshare(CLONE_NEWUSER);
|
||||||
if(ret == -1)
|
if(ret == -1)
|
||||||
{
|
{
|
||||||
@ -956,51 +944,47 @@ static int enter_namespaces(int namespace_options, uid_t namespace_uid, gid_t na
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fd = open("/proc/self/setgroups", O_WRONLY);
|
uid_t current_uid = getuid();
|
||||||
if(fd == -1)
|
gid_t current_gid = getgid();
|
||||||
{
|
|
||||||
EXILE_LOG_ERROR("Failed to open /proc/self/setgroups for writing\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int writesize = snprintf(buf, sizeof(buf), "deny");
|
|
||||||
int writeret = write(fd, buf, writesize);
|
|
||||||
if(writeret < 0 || writeret < writesize)
|
|
||||||
{
|
|
||||||
EXILE_LOG_ERROR("Failed to write to /proc/self/setgroups: %i (%s)\n", writeret, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
fd = open("/proc/self/uid_map", O_WRONLY);
|
FILE *fp = fopen("/proc/self/setgroups", "w");
|
||||||
if(fd == -1)
|
if(fp == NULL)
|
||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("Failed to open /proc/self/uid_map for writing\n");
|
EXILE_LOG_ERROR("fopen failed while trying to deny setgroups\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
writesize = snprintf(buf, sizeof(buf), "%u %u 1\n", namespace_uid, current_uid);
|
if(fprintf(fp, "deny") < 0)
|
||||||
writeret = write(fd, buf, writesize);
|
|
||||||
if(writeret < 0 || writeret < writesize)
|
|
||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("Failed to write to /proc/self/uid_map: %i (%s)\n", writeret, strerror(errno));
|
EXILE_LOG_ERROR("fprintf failed while trying to write setgroups\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
close(fd);
|
fclose(fp);
|
||||||
|
|
||||||
|
fp = fopen("/proc/self/uid_map", "w");
|
||||||
|
if(fp == NULL)
|
||||||
|
{
|
||||||
|
EXILE_LOG_ERROR("fopen failed while trying to write uid_map\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(fprintf(fp, "0 %i", current_uid) < 0)
|
||||||
|
{
|
||||||
|
EXILE_LOG_ERROR("fprintf failed while trying to write uid_map\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
fd = open("/proc/self/gid_map", O_WRONLY);
|
fp = fopen("/proc/self/gid_map", "w");
|
||||||
if(fd == -1)
|
if(fp == NULL)
|
||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("Failed to open /proc/self/gid_map for writing\n");
|
EXILE_LOG_ERROR("fopen failed while trying to write gid_map\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
writesize = snprintf(buf, sizeof(buf), "%u %u 1\n", namespace_gid, current_gid);
|
if(fprintf(fp, "0 %i", current_gid) < 0)
|
||||||
writeret = write(fd, buf, writesize);
|
|
||||||
if(writeret < 0 || writeret < writesize)
|
|
||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("Failed to write to /proc/self/gid_map: %i (%s)\n", writeret, strerror(errno));
|
EXILE_LOG_ERROR("fprintf failed while trying to write gid_map\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
close(fd);
|
fclose(fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(namespace_options & EXILE_UNSHARE_MOUNT)
|
if(namespace_options & EXILE_UNSHARE_MOUNT)
|
||||||
@ -1229,9 +1213,6 @@ static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode)
|
|||||||
if(flags & EXILE_FS_ALLOW_ALL_WRITE)
|
if(flags & EXILE_FS_ALLOW_ALL_WRITE)
|
||||||
{
|
{
|
||||||
result |= LANDLOCK_ACCESS_FS_WRITE_FILE;
|
result |= LANDLOCK_ACCESS_FS_WRITE_FILE;
|
||||||
#ifdef LANDLOCK_ACCESS_FS_TRUNCATE
|
|
||||||
result |= LANDLOCK_ACCESS_FS_TRUNCATE;
|
|
||||||
#endif
|
|
||||||
if(S_ISDIR(statmode))
|
if(S_ISDIR(statmode))
|
||||||
{
|
{
|
||||||
result |= LANDLOCK_ACCESS_FS_REMOVE_DIR;
|
result |= LANDLOCK_ACCESS_FS_REMOVE_DIR;
|
||||||
@ -1241,9 +1222,6 @@ static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode)
|
|||||||
result |= LANDLOCK_ACCESS_FS_MAKE_REG;
|
result |= LANDLOCK_ACCESS_FS_MAKE_REG;
|
||||||
result |= LANDLOCK_ACCESS_FS_MAKE_SOCK;
|
result |= LANDLOCK_ACCESS_FS_MAKE_SOCK;
|
||||||
result |= LANDLOCK_ACCESS_FS_MAKE_SYM;
|
result |= LANDLOCK_ACCESS_FS_MAKE_SYM;
|
||||||
#ifdef LANDLOCK_ACCESS_FS_REFER
|
|
||||||
result |= LANDLOCK_ACCESS_FS_REFER;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(flags & EXILE_FS_ALLOW_EXEC)
|
if(flags & EXILE_FS_ALLOW_EXEC)
|
||||||
@ -1310,42 +1288,15 @@ static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sets maximum values for the handled access fs... */
|
|
||||||
static int landlock_set_max_handled_access(struct landlock_ruleset_attr *ruleset)
|
|
||||||
{
|
|
||||||
int abi = landlock_create_ruleset(NULL, 0,
|
|
||||||
LANDLOCK_CREATE_RULESET_VERSION);
|
|
||||||
if(abi < 0)
|
|
||||||
{
|
|
||||||
EXILE_LOG_ERROR("Can't determine landlock ABI version\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
ruleset->handled_access_net = 0;
|
|
||||||
if(abi == 1)
|
|
||||||
{
|
|
||||||
ruleset->handled_access_fs = ((LANDLOCK_ACCESS_FS_MAKE_SYM << 1) - 1);
|
|
||||||
}
|
|
||||||
if(abi == 2)
|
|
||||||
{
|
|
||||||
ruleset->handled_access_fs = ((LANDLOCK_ACCESS_FS_REFER << 1) - 1);
|
|
||||||
}
|
|
||||||
if(abi >= 3)
|
|
||||||
{
|
|
||||||
ruleset->handled_access_fs = ((LANDLOCK_ACCESS_FS_TRUNCATE << 1) - 1);
|
|
||||||
/* TODO: think about net */
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static int landlock_prepare_ruleset(struct exile_path_policy *policies)
|
static int landlock_prepare_ruleset(struct exile_path_policy *policies)
|
||||||
{
|
{
|
||||||
int ruleset_fd = -1;
|
int ruleset_fd = -1;
|
||||||
struct landlock_ruleset_attr ruleset_attr = {0};
|
struct landlock_ruleset_attr ruleset_attr;
|
||||||
if(landlock_set_max_handled_access(&ruleset_attr) != 0)
|
/* We here want the maximum possible ruleset, so set the var to the max possible bitmask.
|
||||||
{
|
Stolen/Adapted from: [linux src]/security/landlock/limits.h
|
||||||
return -1;
|
*/
|
||||||
}
|
ruleset_attr.handled_access_fs = ((LANDLOCK_ACCESS_FS_MAKE_SYM << 1) - 1);
|
||||||
|
|
||||||
ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||||
if (ruleset_fd < 0)
|
if (ruleset_fd < 0)
|
||||||
{
|
{
|
||||||
@ -1355,7 +1306,7 @@ static int landlock_prepare_ruleset(struct exile_path_policy *policies)
|
|||||||
struct exile_path_policy *policy = policies;
|
struct exile_path_policy *policy = policies;
|
||||||
while(policy != NULL)
|
while(policy != NULL)
|
||||||
{
|
{
|
||||||
struct landlock_path_beneath_attr path_beneath = {0};
|
struct landlock_path_beneath_attr path_beneath;
|
||||||
path_beneath.parent_fd = open(policy->path, O_PATH | O_CLOEXEC);
|
path_beneath.parent_fd = open(policy->path, O_PATH | O_CLOEXEC);
|
||||||
if(path_beneath.parent_fd < 0)
|
if(path_beneath.parent_fd < 0)
|
||||||
{
|
{
|
||||||
@ -1372,13 +1323,6 @@ static int landlock_prepare_ruleset(struct exile_path_policy *policies)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
path_beneath.allowed_access = exile_flags_to_landlock(policy->policy, sb.st_mode);
|
path_beneath.allowed_access = exile_flags_to_landlock(policy->policy, sb.st_mode);
|
||||||
|
|
||||||
/* Required, so the .allowed_access fits .handled_access_fs of the ruleset.
|
|
||||||
* Needed for backwards compatibility, e. g. new binary compiled with new headers,
|
|
||||||
executed on a kernel with an older ABI version which does not have some constant defined...
|
|
||||||
*/
|
|
||||||
path_beneath.allowed_access &= ruleset_attr.handled_access_fs;
|
|
||||||
|
|
||||||
ret = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
|
ret = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
|
||||||
if(ret)
|
if(ret)
|
||||||
{
|
{
|
||||||
@ -1466,11 +1410,6 @@ static int check_policy_sanity(struct exile_policy *policy)
|
|||||||
{
|
{
|
||||||
if(syscall_policy->syscall == EXILE_SYSCALL_MATCH_ALL)
|
if(syscall_policy->syscall == EXILE_SYSCALL_MATCH_ALL)
|
||||||
{
|
{
|
||||||
if(policy->vow_promises != 0)
|
|
||||||
{
|
|
||||||
EXILE_LOG_ERROR("It's not possible to specify a default, all matching syscall policy while also using vows\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
last_match_all = i;
|
last_match_all = i;
|
||||||
match_all_policy = syscall_policy->policy;
|
match_all_policy = syscall_policy->policy;
|
||||||
}
|
}
|
||||||
@ -1481,7 +1420,7 @@ static int check_policy_sanity(struct exile_policy *policy)
|
|||||||
syscall_policy = syscall_policy->next;
|
syscall_policy = syscall_policy->next;
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
if(policy->vow_promises == 0 && (last_match_all == -1 || i - last_match_all != 1))
|
if(last_match_all == -1 || i - last_match_all != 1)
|
||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("The last entry in the syscall policy list must match all syscalls (default rule)\n");
|
EXILE_LOG_ERROR("The last entry in the syscall policy list must match all syscalls (default rule)\n");
|
||||||
return -1;
|
return -1;
|
||||||
@ -1502,20 +1441,7 @@ static void close_file_fds()
|
|||||||
long max_files = sysconf(_SC_OPEN_MAX);
|
long max_files = sysconf(_SC_OPEN_MAX);
|
||||||
for(long i = 3; i <= max_files; i++)
|
for(long i = 3; i <= max_files; i++)
|
||||||
{
|
{
|
||||||
struct stat statbuf;
|
close((int)i);
|
||||||
int fd = (int) max_files;
|
|
||||||
int result = fstat(i, &statbuf);
|
|
||||||
if(result == -1 && errno != EBADF && errno != EACCES)
|
|
||||||
{
|
|
||||||
EXILE_LOG_ERROR("Could not fstat %i: %s\n", fd, strerror(errno));
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
int type = statbuf.st_mode & S_IFMT;
|
|
||||||
if(type != S_IFIFO && type != S_IFSOCK)
|
|
||||||
{
|
|
||||||
/* No error check, retrying not recommended */
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1532,30 +1458,6 @@ static int enable_no_fs(struct exile_policy *policy)
|
|||||||
{
|
{
|
||||||
close_file_fds();
|
close_file_fds();
|
||||||
|
|
||||||
if(exile_landlock_is_available())
|
|
||||||
{
|
|
||||||
struct landlock_ruleset_attr ruleset_attr = {0};
|
|
||||||
if(landlock_set_max_handled_access(&ruleset_attr) != 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
|
||||||
if (ruleset_fd < 0)
|
|
||||||
{
|
|
||||||
EXILE_LOG_ERROR("Failed to create landlock ruleset\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int ret = landlock_restrict_self(ruleset_fd, 0);
|
|
||||||
if(ret != 0)
|
|
||||||
{
|
|
||||||
EXILE_LOG_ERROR("Failed to enable no_fs with landlock: %s\n", strerror(errno));
|
|
||||||
close(ruleset_fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
close(ret);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(chdir("/proc/self/fdinfo") != 0)
|
if(chdir("/proc/self/fdinfo") != 0)
|
||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("Failed to change to safe directory: %s\n", strerror(errno));
|
EXILE_LOG_ERROR("Failed to change to safe directory: %s\n", strerror(errno));
|
||||||
@ -1602,12 +1504,7 @@ int exile_enable_policy(struct exile_policy *policy)
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(policy->keep_fds_open != 1)
|
if(enter_namespaces(policy->namespace_options) < 0)
|
||||||
{
|
|
||||||
close_file_fds();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(enter_namespaces(policy->namespace_options, policy->namespace_uid, policy->namespace_gid) < 0)
|
|
||||||
{
|
{
|
||||||
EXILE_LOG_ERROR("Error while trying to enter namespaces\n");
|
EXILE_LOG_ERROR("Error while trying to enter namespaces\n");
|
||||||
return -1;
|
return -1;
|
||||||
@ -1690,6 +1587,14 @@ int exile_enable_policy(struct exile_policy *policy)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if(policy->no_fs)
|
||||||
|
{
|
||||||
|
if(enable_no_fs(policy) != 0)
|
||||||
|
{
|
||||||
|
EXILE_LOG_ERROR("Failed to take away filesystem access of process\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(policy->no_new_fds)
|
if(policy->no_new_fds)
|
||||||
{
|
{
|
||||||
@ -1701,6 +1606,15 @@ int exile_enable_policy(struct exile_policy *policy)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(policy->drop_caps)
|
||||||
|
{
|
||||||
|
if(drop_caps() < 0)
|
||||||
|
{
|
||||||
|
EXILE_LOG_ERROR("failed to drop capabilities\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(policy->not_dumpable)
|
if(policy->not_dumpable)
|
||||||
{
|
{
|
||||||
if(prctl(PR_SET_DUMPABLE, 0) == -1)
|
if(prctl(PR_SET_DUMPABLE, 0) == -1)
|
||||||
@ -1719,15 +1633,6 @@ int exile_enable_policy(struct exile_policy *policy)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(policy->no_fs)
|
|
||||||
{
|
|
||||||
if(enable_no_fs(policy) != 0)
|
|
||||||
{
|
|
||||||
EXILE_LOG_ERROR("Failed to take away filesystem access of process\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if HAVE_LANDLOCK == 1
|
#if HAVE_LANDLOCK == 1
|
||||||
if (can_use_landlock && policy->path_policies != NULL && landlock_restrict_self(landlock_ruleset_fd, 0) != 0)
|
if (can_use_landlock && policy->path_policies != NULL && landlock_restrict_self(landlock_ruleset_fd, 0) != 0)
|
||||||
{
|
{
|
||||||
@ -1748,19 +1653,12 @@ int exile_enable_policy(struct exile_policy *policy)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(policy->drop_caps)
|
|
||||||
{
|
|
||||||
if(drop_caps() < 0)
|
|
||||||
{
|
|
||||||
EXILE_LOG_ERROR("failed to drop capabilities\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(policy->syscall_policies != NULL)
|
if(policy->syscall_policies != NULL)
|
||||||
{
|
{
|
||||||
return exile_enable_syscall_policy(policy);
|
return exile_enable_syscall_policy(policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1953,6 +1851,13 @@ char *exile_launch_get(struct exile_launch_params *launch_params, size_t *n)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fclose(stream);
|
fclose(stream);
|
||||||
|
int seek = fseek(stream, 0, SEEK_SET);
|
||||||
|
if(seek == -1)
|
||||||
|
{
|
||||||
|
EXILE_LOG_ERROR("fseek failed\n");
|
||||||
|
close(launch_result.read_fd);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
close(launch_result.read_fd);
|
close(launch_result.read_fd);
|
||||||
*n = size;
|
*n = size;
|
||||||
return result;
|
return result;
|
||||||
|
4
exile.h
4
exile.h
@ -364,7 +364,6 @@ struct exile_policy
|
|||||||
int no_new_privs;
|
int no_new_privs;
|
||||||
int no_fs;
|
int no_fs;
|
||||||
int no_new_fds;
|
int no_new_fds;
|
||||||
int keep_fds_open;
|
|
||||||
int namespace_options;
|
int namespace_options;
|
||||||
int disable_syscall_filter;
|
int disable_syscall_filter;
|
||||||
/* Bind mounts all paths in path_policies into the chroot and applies
|
/* Bind mounts all paths in path_policies into the chroot and applies
|
||||||
@ -375,9 +374,6 @@ struct exile_policy
|
|||||||
|
|
||||||
uint64_t vow_promises;
|
uint64_t vow_promises;
|
||||||
|
|
||||||
uid_t namespace_uid;
|
|
||||||
gid_t namespace_gid;
|
|
||||||
|
|
||||||
/* Do not manually add policies here, use exile_append_path_policies() */
|
/* Do not manually add policies here, use exile_append_path_policies() */
|
||||||
struct exile_path_policy *path_policies;
|
struct exile_path_policy *path_policies;
|
||||||
struct exile_path_policy **path_policies_tail;
|
struct exile_path_policy **path_policies_tail;
|
||||||
|
148
test.c
148
test.c
@ -618,9 +618,9 @@ int test_launch_get()
|
|||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
char *content = exile_launch_get(¶ms, &n);
|
char *content = exile_launch_get(¶ms, &n);
|
||||||
unsigned int len = strlen(LAUNCH_GET_TEST_STR);
|
unsigned int len = strlen(LAUNCH_GET_TEST_STR);
|
||||||
if(n != len)
|
if(n != strlen(LAUNCH_GET_TEST_STR))
|
||||||
{
|
{
|
||||||
LOG("Lenght does not match: %lu vs %u\n", n, len);
|
LOG("Lenght does does not match: %lu vs %u\n", n, len);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(strcmp(content, LAUNCH_GET_TEST_STR) != 0)
|
if(strcmp(content, LAUNCH_GET_TEST_STR) != 0)
|
||||||
@ -661,146 +661,6 @@ int test_clone3_nosys()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int do_test_nsuidmap(const char *path, const char *firstfield, const char *secondfield, const char *thirdfield)
|
|
||||||
{
|
|
||||||
char *line = NULL;
|
|
||||||
size_t n = 0;
|
|
||||||
FILE *fp = fopen(path, "r");
|
|
||||||
|
|
||||||
int ret = getdelim(&line, &n, ' ', fp);
|
|
||||||
while(ret != -1 && strlen(line) == 1 && *line == ' ')
|
|
||||||
ret = getdelim(&line, &n, ' ', fp);
|
|
||||||
if(ret == -1)
|
|
||||||
{
|
|
||||||
LOG("getdelim() failed to read a line from %s\n", path);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
line[ret-1] = '\0';
|
|
||||||
if(strcmp(line, firstfield) != 0)
|
|
||||||
{
|
|
||||||
LOG("Invalid value for first entry in map: Expected: %s, was: %s\n", firstfield, line);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = getdelim(&line, &n, ' ', fp);
|
|
||||||
while(ret != -1 && strlen(line) == 1 && *line == ' ')
|
|
||||||
ret = getdelim(&line, &n, ' ', fp);
|
|
||||||
if(ret == -1)
|
|
||||||
{
|
|
||||||
LOG("getdelim() failed to read a line from map\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
line[ret-1] = '\0';
|
|
||||||
|
|
||||||
if(strcmp(line, secondfield) != 0)
|
|
||||||
{
|
|
||||||
LOG("Invalid value for second entry in map: Expected: %s, was: %s\n", secondfield, line);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ret = getdelim(&line, &n, ' ', fp);
|
|
||||||
while(ret != -1 && strlen(line) == 1 && *line == ' ')
|
|
||||||
ret = getdelim(&line, &n, ' ', fp);
|
|
||||||
if(ret == -1)
|
|
||||||
{
|
|
||||||
LOG("getdelim() failed to read a line from uid_map\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
line[ret-1] = '\0';
|
|
||||||
if(strcmp(line, thirdfield) != 0)
|
|
||||||
{
|
|
||||||
LOG("Invalid value for second entry in map: Expected: %s, was: %s\n", thirdfield, line);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int test_unshare_user()
|
|
||||||
{
|
|
||||||
char uidstr[64];
|
|
||||||
snprintf(uidstr, sizeof(uidstr), "%u", getuid());
|
|
||||||
|
|
||||||
char gidstr[64];
|
|
||||||
snprintf(gidstr, sizeof(gidstr), "%u", getgid());
|
|
||||||
|
|
||||||
struct exile_policy *policy = exile_init_policy();
|
|
||||||
policy->namespace_options = EXILE_UNSHARE_USER;
|
|
||||||
xexile_enable_policy(policy);
|
|
||||||
|
|
||||||
if(do_test_nsuidmap("/proc/self/uid_map", "0", uidstr, "1") != 0)
|
|
||||||
{
|
|
||||||
LOG("/proc/self/uid_map failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(do_test_nsuidmap("/proc/self/gid_map", "0", gidstr, "1") != 0)
|
|
||||||
{
|
|
||||||
LOG("/proc/self/gid_map failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *fp = fopen("/proc/self/setgroups", "r");
|
|
||||||
|
|
||||||
char buffer[4096] = { 0 };
|
|
||||||
fread(buffer, sizeof(buffer), 1, fp);
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
if(strcmp(buffer, "deny\n") != 0)
|
|
||||||
{
|
|
||||||
LOG("/proc/self/setgroups does not contain 'deny'\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int test_unshare_user_own_uid()
|
|
||||||
{
|
|
||||||
uid_t uid = getuid();
|
|
||||||
gid_t gid = getgid();
|
|
||||||
|
|
||||||
char uidstr[64];
|
|
||||||
snprintf(uidstr, sizeof(uidstr), "%u", uid);
|
|
||||||
|
|
||||||
char gidstr[64];
|
|
||||||
snprintf(gidstr, sizeof(gidstr), "%u", gid);
|
|
||||||
|
|
||||||
struct exile_policy *policy = exile_init_policy();
|
|
||||||
policy->namespace_options = EXILE_UNSHARE_USER;
|
|
||||||
policy->namespace_gid = gid;
|
|
||||||
policy->namespace_uid = uid;
|
|
||||||
xexile_enable_policy(policy);
|
|
||||||
|
|
||||||
if(do_test_nsuidmap("/proc/self/uid_map", uidstr, uidstr, "1") != 0)
|
|
||||||
{
|
|
||||||
LOG("/proc/self/uid_map failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(do_test_nsuidmap("/proc/self/gid_map", gidstr, gidstr, "1") != 0)
|
|
||||||
{
|
|
||||||
LOG("/proc/self/gid_map failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *fp = fopen("/proc/self/setgroups", "r");
|
|
||||||
|
|
||||||
char buffer[4096] = { 0 };
|
|
||||||
fread(buffer, sizeof(buffer), 1, fp);
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
if(strcmp(buffer, "deny\n") != 0)
|
|
||||||
{
|
|
||||||
LOG("/proc/self/setgroups does not contain 'deny'\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dispatcher
|
struct dispatcher
|
||||||
{
|
{
|
||||||
char *name;
|
char *name;
|
||||||
@ -829,10 +689,6 @@ struct dispatcher dispatchers[] = {
|
|||||||
{ "launch-get", &test_launch_get},
|
{ "launch-get", &test_launch_get},
|
||||||
{ "vow_from_str", &test_vows_from_str},
|
{ "vow_from_str", &test_vows_from_str},
|
||||||
{ "clone3_nosys", &test_clone3_nosys},
|
{ "clone3_nosys", &test_clone3_nosys},
|
||||||
{ "unshare-user", &test_unshare_user},
|
|
||||||
{ "unshare-user-own-uid", &test_unshare_user_own_uid},
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
|
23
test.sh
23
test.sh
@ -8,41 +8,41 @@ COUNT_SUCCEEDED=0
|
|||||||
COUNT_FAILED=0
|
COUNT_FAILED=0
|
||||||
COUNT_SKIPPED=0
|
COUNT_SKIPPED=0
|
||||||
|
|
||||||
print_fail()
|
function print_fail()
|
||||||
{
|
{
|
||||||
printf "${RED}$@${NC}\n" 1>&2
|
echo -e "${RED}$@${NC}" 1>&2
|
||||||
}
|
}
|
||||||
|
|
||||||
print_success()
|
function print_success()
|
||||||
{
|
{
|
||||||
printf "${GREEN}$@${NC}\n"
|
echo -e "${GREEN}$@${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
print_skipped()
|
function print_skipped()
|
||||||
{
|
{
|
||||||
printf "${YELLOW}$@${NC}\n"
|
echo -e "${YELLOW}$@${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
runtest_fail()
|
function runtest_fail()
|
||||||
{
|
{
|
||||||
print_fail "failed"
|
print_fail "failed"
|
||||||
COUNT_FAILED=$(($COUNT_FAILED+1))
|
COUNT_FAILED=$(($COUNT_FAILED+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
runtest_success()
|
function runtest_success()
|
||||||
{
|
{
|
||||||
print_success "ok"
|
print_success "ok"
|
||||||
COUNT_SUCCEEDED=$((COUNT_SUCCEEDED+1))
|
COUNT_SUCCEEDED=$((COUNT_SUCCEEDED+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
runtest_skipped()
|
function runtest_skipped()
|
||||||
{
|
{
|
||||||
print_skipped "skipped"
|
print_skipped "skipped"
|
||||||
COUNT_SKIPPED=$((COUNT_SKIPPED+1))
|
COUNT_SKIPPED=$((COUNT_SKIPPED+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
runtest()
|
function runtest()
|
||||||
{
|
{
|
||||||
testbin="$1"
|
testbin="$1"
|
||||||
testname="$2"
|
testname="$2"
|
||||||
@ -52,8 +52,7 @@ runtest()
|
|||||||
|
|
||||||
echo -n "Running $testname... "
|
echo -n "Running $testname... "
|
||||||
#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
|
#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
|
||||||
(./$testbin "$testname" || exit $?) >> "${test_log_file}" 2>&1
|
(./$testbin "$testname" || exit $?) &>> "${test_log_file}"
|
||||||
|
|
||||||
ret=$?
|
ret=$?
|
||||||
SUCCESS="no"
|
SUCCESS="no"
|
||||||
if [ $ret -eq 0 ] ; then
|
if [ $ret -eq 0 ] ; then
|
||||||
|
Loading…
Reference in New Issue
Block a user