blob: 7a84eba0581a0c41952934fddf9c45f70cb6905b [file] [log] [blame]
/*
* Copyright (C) 2017 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it would be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define TST_NO_DEFAULT_MAIN
#include "tst_test.h"
#include "lapi/fallocate.h"
#include "lapi/fcntl.h"
#include "lapi/memfd.h"
#include "lapi/syscalls.h"
#include "memfd_create_common.h"
int sys_memfd_create(const char *name, unsigned int flags)
{
return tst_syscall(__NR_memfd_create, name, flags);
}
int check_fallocate(const char *filename, const int lineno, int fd,
int mode, off_t offset, off_t len)
{
int r;
r = fallocate(fd, mode, offset, len);
if (r < 0) {
tst_brk_(filename, lineno, TFAIL | TERRNO,
"fallocate(%d, %d, %ld, %ld) failed", fd, mode,
offset, len);
}
tst_res_(filename, lineno, TPASS,
"fallocate(%d, %d, %ld, %ld) succeeded", fd, mode,
offset, len);
return r;
}
int check_fallocate_fail(const char *filename, const int lineno, int fd,
int mode, off_t offset, off_t len)
{
int r;
r = fallocate(fd, mode, offset, len);
if (r >= 0) {
tst_res_(filename, lineno, TFAIL,
"fallocate(%d, %d, %ld, %ld) succeeded unexpectedly",
fd, mode, offset, len);
return r;
}
tst_res_(filename, lineno, TPASS | TERRNO,
"fallocate(%d, %d, %ld, %ld) failed as expected", fd,
mode, offset, len);
return r;
}
void check_ftruncate(const char *filename, const int lineno, int fd,
off_t length)
{
safe_ftruncate(filename, lineno, fd, length);
tst_res_(filename, lineno, TPASS, "ftruncate(%d, %ld) succeeded", fd,
length);
}
void check_ftruncate_fail(const char *filename, const int lineno,
int fd, off_t length)
{
if (ftruncate(fd, length) >= 0) {
tst_res_(filename, lineno, TFAIL,
"ftruncate(%d, %ld) succeeded unexpectedly",
fd, length);
return;
}
tst_res_(filename, lineno, TPASS | TERRNO,
"ftruncate(%d, %ld) failed as expected", fd, length);
}
int get_mfd_all_available_flags(const char *filename, const int lineno)
{
unsigned int i;
int flag;
int flags2test[] = FLAGS_ALL_ARRAY_INITIALIZER;
int flags_available = 0;
if (!MFD_FLAGS_AVAILABLE(0)) {
tst_brk_(filename, lineno, TCONF,
"memfd_create(0) not implemented");
}
for (i = 0; i < ARRAY_SIZE(flags2test); i++) {
flag = flags2test[i];
if (MFD_FLAGS_AVAILABLE(flag))
flags_available |= flag;
}
return flags_available;
}
int mfd_flags_available(const char *filename, const int lineno,
unsigned int flags)
{
TEST(sys_memfd_create("dummy_call", flags));
if (TST_RET < 0) {
if (TST_ERR != EINVAL) {
tst_brk_(filename, lineno, TBROK | TTERRNO,
"memfd_create() failed");
}
return 0;
}
SAFE_CLOSE(TST_RET);
return 1;
}
int check_mfd_new(const char *filename, const int lineno,
const char *name, loff_t sz, int flags)
{
int fd;
fd = sys_memfd_create(name, flags);
if (fd < 0) {
tst_brk_(filename, lineno, TBROK | TERRNO,
"memfd_create(%s, %d) failed", name, flags);
}
tst_res_(filename, lineno, TPASS, "memfd_create(%s, %d) succeeded",
name, flags);
check_ftruncate(filename, lineno, fd, sz);
return fd;
}
void check_mfd_fail_new(const char *filename, const int lineno,
const char *name, int flags)
{
int fd;
fd = sys_memfd_create(name, flags);
if (fd >= 0) {
safe_close(filename, lineno, NULL, fd);
tst_brk_(filename, lineno, TFAIL,
"memfd_create(%s, %d) succeeded unexpectedly",
name, flags);
}
tst_res_(filename, lineno, TPASS | TERRNO,
"memfd_create(%s, %d) failed as expected", name, flags);
}
void *check_mmap(const char *file, const int lineno, void *addr, size_t length,
int prot, int flags, int fd, off_t offset)
{
void *p;
p = safe_mmap(file, lineno, addr, length, prot, flags, fd, offset);
tst_res_(file, lineno, TPASS,
"mmap(%p, %zu, %i, %i, %i, %li) succeeded", addr,
length, prot, flags, fd, (long)offset);
return p;
}
void check_mmap_fail(const char *file, const int lineno, void *addr,
size_t length, int prot, int flags, int fd, off_t offset)
{
if (mmap(addr, length, prot, flags, fd, offset) != MAP_FAILED) {
safe_munmap(file, lineno, NULL, addr, length);
tst_res_(file, lineno, TFAIL,
"mmap(%p, %zu, %i, %i, %i, %li) succeeded unexpectedly",
addr, length, prot, flags, fd, (long)offset);
return;
}
tst_res_(file, lineno, TPASS | TERRNO,
"mmap(%p, %zu, %i, %i, %i, %li) failed as expected",
addr, length, prot, flags, fd, (long)offset);
}
void check_munmap(const char *file, const int lineno, void *p, size_t length)
{
safe_munmap(file, lineno, NULL, p, length);
tst_res_(file, lineno, TPASS, "munmap(%p, %zu) succeeded", p, length);
}
void check_mfd_has_seals(const char *file, const int lineno, int fd, int seals)
{
int ret = SAFE_FCNTL((fd), F_GET_SEALS);
if (ret != seals) {
tst_brk_(file, lineno, TFAIL,
"fd %d doesn't have expected seals (%d expected %d)",
fd, ret, seals);
}
tst_res_(file, lineno, TPASS,
"fd %d has expected seals (%d)", fd, seals);
}
void check_mprotect(const char *file, const int lineno, void *addr,
size_t length, int prot)
{
if (mprotect(addr, length, prot) < 0) {
tst_brk_(file, lineno, TFAIL | TERRNO,
"mprotect(%p, %zu, %d) failed", addr, length, prot);
}
tst_res_(file, lineno, TPASS, "mprotect(%p, %zu, %d) succeeded", addr,
length, prot);
}
void check_mfd_fail_add_seals(const char *filename, const int lineno,
int fd, int seals)
{
if (fcntl(fd, F_ADD_SEALS, seals) >= 0) {
tst_brk_(filename, lineno, TFAIL,
"fcntl(%d, F_ADD_SEALS) succeeded unexpectedly", fd);
}
tst_res_(filename, lineno, TPASS | TERRNO,
"fcntl(%d, F_ADD_SEALS, %d) failed as expected", (fd),
(seals));
}
void check_mfd_size(const char *filename, const int lineno, int fd,
size_t size)
{
struct stat st;
safe_fstat(filename, lineno, fd, &st);
if (st.st_size != (long)size) {
tst_brk_(filename, lineno, TFAIL,
"fstat(%d, &st): unexpected file size", fd);
}
tst_res_(filename, lineno, TPASS,
"fstat(%d, &st): file size is correct", fd);
}
int check_mfd_open(const char *filename, const int lineno, int fd,
int flags, mode_t mode)
{
int r;
char buf[512];
sprintf(buf, "/proc/self/fd/%d", fd);
r = safe_open(filename, lineno, NULL, buf, flags, mode);
tst_res_(filename, lineno, TPASS, "open(%s, %d, %d) succeeded", buf,
flags, mode);
return r;
}
void check_mfd_fail_open(const char *filename, const int lineno, int fd,
int flags, mode_t mode)
{
char buf[512];
sprintf(buf, "/proc/self/fd/%d", fd);
fd = open(buf, flags, mode);
if (fd > 0) {
safe_close(filename, lineno, NULL, fd);
tst_res_(filename, lineno, TFAIL,
"open(%s, %d, %d) succeeded unexpectedly", buf,
flags, mode);
} else {
tst_res_(filename, lineno, TPASS | TERRNO,
"open(%s, %d, %d) failed as expected", buf,
flags, mode);
}
}
void check_mfd_readable(const char *filename, const int lineno, int fd)
{
char buf[16];
void *p;
safe_read(filename, lineno, NULL, 1, fd, buf, sizeof(buf));
tst_res_(filename, lineno, TPASS, "read(%d, %s, %zu) succeeded", fd,
buf, sizeof(buf));
/* verify PROT_READ *is* allowed */
p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE, PROT_READ,
MAP_PRIVATE, fd, 0);
check_munmap(filename, lineno, p, MFD_DEF_SIZE);
/* verify MAP_PRIVATE is *always* allowed (even writable) */
p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE,
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
check_munmap(filename, lineno, p, MFD_DEF_SIZE);
}
void check_mfd_writeable(const char *filename, const int lineno, int fd)
{
void *p;
/* verify write() succeeds */
safe_write(filename, lineno, NULL, 1, fd, "\0\0\0\0", 4);
tst_res_(filename, lineno, TPASS, "write(%d, %s, %d) succeeded", fd,
"\\0\\0\\0\\0", 4);
/* verify PROT_READ | PROT_WRITE is allowed */
p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
*(char *)p = 0;
check_munmap(filename, lineno, p, MFD_DEF_SIZE);
/* verify PROT_WRITE is allowed */
p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE,
PROT_WRITE, MAP_SHARED, fd, 0);
*(char *)p = 0;
check_munmap(filename, lineno, p, MFD_DEF_SIZE);
/* verify PROT_READ with MAP_SHARED is allowed and a following
* mprotect(PROT_WRITE) allows writing
*/
p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE,
PROT_READ, MAP_SHARED, fd, 0);
check_mprotect(filename, lineno, p, MFD_DEF_SIZE,
PROT_READ | PROT_WRITE);
*(char *)p = 0;
check_munmap(filename, lineno, p, MFD_DEF_SIZE);
/* verify PUNCH_HOLE works */
check_fallocate(filename, lineno, fd,
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0,
MFD_DEF_SIZE);
}
void check_mfd_non_writeable(const char *filename, const int lineno,
int fd)
{
void *p;
/* verify write() fails */
TEST(write(fd, "data", 4));
if (TST_RET < 0) {
if (TST_ERR != EPERM) {
tst_brk_(filename, lineno, TFAIL | TTERRNO,
"write() didn't fail as expected");
}
} else {
tst_brk_(filename, lineno, TFAIL,
"write() succeeded unexpectedly");
}
tst_res_(filename, lineno, TPASS | TTERRNO, "write failed as expected");
/* verify PROT_READ | PROT_WRITE is not allowed */
check_mmap_fail(filename, lineno, NULL, MFD_DEF_SIZE,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
/* verify PROT_WRITE is not allowed */
check_mmap_fail(filename, lineno, NULL, MFD_DEF_SIZE,
PROT_WRITE, MAP_SHARED, fd, 0);
/* Verify PROT_READ with MAP_SHARED with a following mprotect is not
* allowed. Note that for r/w the kernel already prevents the mmap.
*/
p = mmap(NULL, MFD_DEF_SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (p != MAP_FAILED) {
if (mprotect(p, MFD_DEF_SIZE, PROT_READ | PROT_WRITE) >= 0) {
tst_brk_(filename, lineno, TFAIL | TERRNO,
"mmap()+mprotect() succeeded unexpectedly");
}
}
/* verify PUNCH_HOLE fails */
check_fallocate_fail(filename, lineno, fd,
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0,
MFD_DEF_SIZE);
}
void check_mfd_shrinkable(const char *filename, const int lineno, int fd)
{
int fd2;
check_ftruncate(filename, lineno, fd, MFD_DEF_SIZE / 2);
check_mfd_size(filename, lineno, fd, MFD_DEF_SIZE / 2);
fd2 = check_mfd_open(filename, lineno, fd,
O_RDWR | O_CREAT | O_TRUNC, 0600);
safe_close(filename, lineno, NULL, fd2);
check_mfd_size(filename, lineno, fd, 0);
}
void check_mfd_non_shrinkable(const char *filename, const int lineno, int fd)
{
check_ftruncate_fail(filename, lineno, fd, MFD_DEF_SIZE / 2);
check_mfd_fail_open(filename, lineno, fd,
O_RDWR | O_CREAT | O_TRUNC, 0600);
}
void check_mfd_growable(const char *filename, const int lineno, int fd)
{
check_ftruncate(filename, lineno, fd, MFD_DEF_SIZE * 2);
check_mfd_size(filename, lineno, fd, MFD_DEF_SIZE * 2);
check_fallocate(filename, lineno, fd, 0, 0, MFD_DEF_SIZE * 4);
check_mfd_size(filename, lineno, fd, MFD_DEF_SIZE * 4);
}
void check_mfd_non_growable(const char *filename, const int lineno, int fd)
{
check_ftruncate_fail(filename, lineno, fd, MFD_DEF_SIZE * 2);
check_fallocate_fail(filename, lineno, fd, 0, 0, MFD_DEF_SIZE * 4);
}
void check_mfd_growable_by_write(const char *filename, const int lineno, int fd)
{
char buf[MFD_DEF_SIZE * 8];
if (pwrite(fd, buf, sizeof(buf), 0) != sizeof(buf)) {
tst_res_(filename, lineno, TFAIL | TERRNO,
"pwrite(%d, %s, %zu, %d) failed",
fd, buf, sizeof(buf), 0);
return;
}
tst_res_(filename, lineno, TPASS, "pwrite(%d, %s, %zu, %d) succeeded",
fd, buf, sizeof(buf), 0);
check_mfd_size(filename, lineno, fd, MFD_DEF_SIZE * 8);
}
void check_mfd_non_growable_by_write(const char *filename, const int lineno,
int fd)
{
char buf[MFD_DEF_SIZE * 8];
if (pwrite(fd, buf, sizeof(buf), 0) == sizeof(buf)) {
tst_res_(filename, lineno, TFAIL,
"pwrite(%d, %s, %zu, %d) didn't fail as expected",
fd, buf, sizeof(buf), 0);
return;
}
tst_res_(filename, lineno, TPASS, "pwrite(%d, %s, %zu, %d) succeeded",
fd, buf, sizeof(buf), 0);
}