You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

99 lines
3.6 KiB

#include <sys/syscall.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define IFUNC_PREFIX _$ifun__
#define IFUNC_IMPL_PREFIX _$impl__
#include "ifunc.h"
#include <memfd_secret.h>
#define READ_ONCE(slot) ((__typeof__(slot))(*(const volatile __typeof__(slot)*)&(slot)))
#define WRITE_ONCE(slot, value) (*((volatile __typeof__(slot)*)&(slot)) = (value))
__attribute__((gnu_inline/*, always_inline*/))
static inline
int _memfd_secret_raw(unsigned int flags)
{
return syscall(SYS_memfd_secret, flags);
}
int _memfd_secret(unsigned int flags)
{
return _memfd_secret_raw(flags);
}
__attribute__((gnu_inline))
static inline
int _has_memfd_secret_raw()
{
// Attempt syscall
int fd = _memfd_secret(0); //XXX: NOTE: man page says `FD_CLOEXEC` is a valid flag, but using it returns `EINVAL`?
// If failure to create new fd was caused by `ENOSYS`, it is not available.
if(fd < 0 && errno == ENOSYS)
return 0;
// If `fd` returned was valid, close it.
else if(fd >= 0) close(fd);
return 1;
}
static int _$has_memfd_secret = -1; //XXX: I don't think this needs to be _Atomic... But it might... (TODO: Maybe change this to use `call_once()` instead?)
// NOTE: Making this `static` visible to TU allows IFUNC resolver to also set it, and therefore `_has_memfd_secret_raw()` may never need to be called. (XXX: Unless resolver itself calls `_has_memfd_secret()` (below V) in which case the exposure isn't needed and this can be moved back to the function body as `has` to de-clutter TU namespace a bit.)
int _has_memfd_secret() {
#define has _$has_memfd_secret
int ok;
if(__builtin_expect( (ok = READ_ONCE(has)) == -1, 0)) {
ok = _has_memfd_secret_raw();
WRITE_ONCE(has, ok);
//TODO: How to add memory barrier after `WRITE_ONCE()` call? Look up GCC's memory barrier builtin. (XXX: If we're doing that, we may as well do as said above and change this to use `call_once()` to set or a `relaxed` atomic, idk... This function shouldn't be called much anyway, so.
return ok;
}
#undef has
return ok;
}
int IFUNC_DEF(memfd_secret, (unsigned int));
/// Set as IFUNC target for systems with `memfd_secret()` support enabled.
__attribute__((visibility("hidden")))
int IFUNC_IMPL(memfd_secret, $enabled) (unsigned int flags)
{
return _memfd_secret_raw(flags);
}
/// Set as IFUNC target for systems *without* `memfd_secret()` support enabled.
///
/// The call is forwarded to `memfd_create()` instead.
__attribute__((visibility("hidden")))
int IFUNC_IMPL(memfd_secret, $disabled) (unsigned int flags)
{
// Translate mask `flags`, from `FD_CLOEXEC` (if it is set) -> `MEMFD_CLOEXEC`.
if( FD_CLOEXEC != MFD_CLOEXEC ) { // NOTE: This is a constant expression, and this code will be removed if they are equal.
// Check if all bit(s) of `FD_CLOEXEC` is in `flags`.
if((flags & FD_CLOEXEC) == FD_CLOEXEC) {
// Mask out the `FD_CLOEXEC` bit(s)
flags &= ~FD_CLOEXEC;
// Mask in the `MFD_CLOEXEC` bit(s)
flags |= MFD_CLOEXEC;
} // NOTE: We do not need to check cases where `flags & FD_CLOEXEC` is non-zero but the above branch is not hit, that would be an invalid call anyway. Plus I highly doubt any system will set `FD_CLOEXEC` to be more than 1 set bit anyway.
}
return memfd_create("memfd_secret@?", flags);
}
int IFUNC_RESOLVER(memfd_secret) (unsigned int flags)
{
return _has_memfd_secret()
? &IFUNC_NAME(memfd_secret, $enabled)
: &IFUNC_NAME(memfd_secret, $disabled);
}
//TODO: IFUNC resolver for `memfd_secret()` (Above two targets^^)
//TODO: Use `ifunc.h`'s macros for defining the ifunc instead of manually, it de-clutters shit and removed repetition.