WSL/test/linux/unit_tests/vfsaccess.c
Josh Soref abce91d14b
Spelling (#12954)
* link: Collect WSL logs (recommended method)

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* link: Advanced Authoring Tests in C++

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* link: CMake Documentation and Community

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* link: Collect WSL logs for networking issues

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* link: Collect WSL logs (recommended method)

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: ; otherwise,

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: a

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: access

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: accessible

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: across

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: actively

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: adapters

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: address

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: addresses

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: and

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: appropriate

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: argument

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: associated

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: attach

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: available

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: beginning

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: between

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: binaries

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: bound

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: buffer

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: buffers

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: cannot

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: canonical

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: capabilities

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: case-insensitive

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: case-sensitive

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: certified

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: command

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: committer

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: communication

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: complains

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: configuration

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: consumed

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: continue

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: converted

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: currently

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: customers

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: daemon

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: deferred

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: definitions

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: delimiter

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: delivered

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: dellink

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: derived

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: descriptor

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: destined

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: destruct

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: destructible

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: destructor

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: detach

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: differentiate

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: directories

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: disassociate

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: disposition

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: distribution

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: distro

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: duping

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: emitted

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: empty

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: environment

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: every time

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: exclusive

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: expected

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: expire

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: explicitly

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: fall back

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: false

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: fastfail

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: filesystem

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: first

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: followed

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: for

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: functionality

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: functionally

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: github

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: greater

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: guarantee

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: guaranteed

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: handles

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: hangup

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: hierarchy

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: hogwarts

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: hydrated

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: icrnl

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: implementation

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: implementing

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: initialize

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: instance

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: instantiate

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: instantiations

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: intentionally

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: interpret

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: interpreter

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: irreversibly

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: iteration

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: iterator

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: its

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: kernel

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: kmsg

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: knowledge

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: maximum

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: mirrored

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: msftconnecttest

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: multi

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: multiple

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: mutable

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: namespace

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: nonexistent

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: notifications

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: occurred

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: occurring

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: otherwise,

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: outstanding

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: overridden

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: partition

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: pass through

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: passthrough

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: performs

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: periodically

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: positional

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: precedence

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: preexisting

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: preferring

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: prepopulate

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: previous

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: privileges

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: process

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: processes

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: programmatically

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: protection

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: provided

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: reasonable

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: receive

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: received

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: red hat

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: reentrant

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: registered

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: regularly

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: relay

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: release

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: representing

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: requests

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: response

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: resurrect

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: retention

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: returned

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: security

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: semaphore

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: separate

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: separator

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: service

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: set up

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: setup

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: severely

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: should

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: signal

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: similarly

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: simple

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: simplified

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: single

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: specified

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: splitting

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: standard

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: stress

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: succeed

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: success

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: successfully

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: supplementary

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: synced

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: system

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: take

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: than

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: that opening

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: the

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: threadpool

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: to

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: true

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: truncate

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: tunneling

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: unexpected

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: uninitialize

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: unique

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: unprivileged

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: unregistered

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: untrusted

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: upgrade

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: utility

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: validating

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: variant

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: variation

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: variations

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: verify

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: visible

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: whether

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: winget

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: worker

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: written

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* spelling: wslservice

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* format source

---------

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Co-authored-by: Ben Hillis <benhillis@gmail.com>
Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>
2025-07-23 15:19:00 -07:00

2613 lines
72 KiB
C

/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
vfsaccess.c
Abstract:
This file is a vfs access permissions test.
--*/
#include "lxtcommon.h"
#include "unittests.h"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <utime.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/wait.h>
#include <limits.h>
#include "lxtfs.h"
#if !defined(__amd64__) && !defined(__aarch64__)
#include <sys/capability.h>
#else
#include <sys/cdefs.h>
#include <linux/capability.h>
#define _LINUX_CAPABILITY_VERSION_3 0x20080522
#ifndef O_PATH
#define O_PATH 010000000
#endif
#endif
#include <sys/vfs.h>
#include <linux/prctl.h>
#include <stdio.h>
#define LXT_NAME "vfsaccess"
#define LXT_NAME_DRVFS "vfsaccess_drvfs"
#define VFS_FILE_OBJECT_COUNT LXT_COUNT_OF(g_VfsFileObjectFlags)
#define VFS_FILE_CONTENTS "vfsaccesstestfilecontents"
#define VFS_ACCESS_UID 1012
#define VFS_ACCESS_PARENT_DIR "/data/test/vfsaccesstest"
#define VFS_ACCESS_CHMOD_DIR VFS_ACCESS_PARENT_DIR "/vfsaccessdir_chmod"
#define VFS_ACCESS_OPATH_DIR VFS_ACCESS_PARENT_DIR "/vfsaccessopathdir"
#define VFS_ACCESS_OPATH_FILE VFS_ACCESS_PARENT_DIR "/vfsaccessopath"
#define VFS_ACCESS_OPATH_FILE_LINK VFS_ACCESS_PARENT_DIR "/vfsaccessopathlink"
#define VFS_ACCESS_STICKY_BIT_DIR VFS_ACCESS_PARENT_DIR "/vfsaccessdir_stickybit"
#define VFS_ACCESS_GROUP_USER_ID_DIR VFS_ACCESS_PARENT_DIR "/vfsaccessdir_groupuserid"
#define VFS_ACCESS_UTIME_FILE VFS_ACCESS_PARENT_DIR "/vfsaccesutime"
#define VFS_ACCESS_FSUID_FILE VFS_ACCESS_PARENT_DIR "/setfsuid_testfile"
#define VFS_ACCESS_FIFO VFS_ACCESS_PARENT_DIR "/vfsaccess_fifo"
#define O_NOACCESS (O_WRONLY | O_RDWR)
#define VFS_ACCESS_EXECVE_TEST_RESULT (123)
typedef struct _VFS_ACCESS_FILE
{
char* Name;
mode_t Mode;
} VFS_ACCESS_FILE, *PVFS_ACCESS_FILE;
typedef struct _VFS_ACCESS_FILE_OBJECT
{
int Fd;
int Flags;
} VFS_ACCESS_FILE_OBJECT, *PVFS_ACCESS_FILE_OBJECT;
struct reuid_t
{
uid_t r;
uid_t e;
uid_t s;
};
int VfsAccessCheckResult(int ResultActual, int ResultExpected, int ErrnoActual, int ErrnoExpected, char* Message, int VariationIndex);
void VfsAccessFileObjectCleanup(void);
int VfsAccessFileObjectCreateFiles(void);
int VfsAccessFileObjectCreateSymlinks(void);
int VfsAccessFileObjectOpenFiles(VFS_ACCESS_FILE_OBJECT Files[]);
int VfsAccessFileObjectOpenSymlinks(VFS_ACCESS_FILE_OBJECT Files[]);
int VfsAccessFileObjectChecks(PLXT_ARGS Args);
int VfsAccessFileObjectSymlinksChecks(PLXT_ARGS Args);
int VfsAccessRemapReference(PLXT_ARGS Args);
int VfsAccessChmod(PLXT_ARGS Args);
int VfsAccessChmodCap(PLXT_ARGS Args);
LXT_VARIATION_HANDLER VfsAccessFifo;
int VfsAccessOPath(PLXT_ARGS Args);
int VfsAccessStickyBit(PLXT_ARGS Args);
int VfsAccessSetUserGroupId(PLXT_ARGS Args);
int VfsAccessSetUserGroupIdExecveChild(void);
int VfsAccessInodeChecks(PLXT_ARGS Args);
int VfsAccessParseArgs(int Argc, char* Argv[], LXT_ARGS* Args);
int VfsAccessUTimeCap(PLXT_ARGS Args);
int VfsAccessSetFsUid(PLXT_ARGS Args);
int VfsAccessSetUid(PLXT_ARGS Args);
//
// Global constants
//
#define VFS_ACCESS_FILE_OBJECT_FILE 0
#define VFS_ACCESS_REMAP_FILE 1
static const VFS_ACCESS_FILE g_VfsFiles[] = {
{VFS_ACCESS_PARENT_DIR "/vfsaccessfile", S_IRWXU | S_IRWXG | S_IRWXO},
{VFS_ACCESS_PARENT_DIR "/vfsaccessfile_remap", S_IRWXU | S_IRWXG | S_IRWXO}};
static const char* g_VfsSymlinks[] = {
VFS_ACCESS_PARENT_DIR "/sym_vfsaccessfile", VFS_ACCESS_PARENT_DIR "/sym_vfsaccessfile_remap"};
static const int g_VfsFileObjectFlags[] = {O_RDONLY, O_WRONLY, O_RDWR, O_NOACCESS, O_RDONLY | O_PATH, O_RDWR | O_APPEND};
static const VFS_ACCESS_FILE g_VfsInodeEntries[] = {
{VFS_ACCESS_PARENT_DIR "/vfsaccessfile_r", S_IFREG | S_IRUSR | S_IRGRP | S_IROTH},
{VFS_ACCESS_PARENT_DIR "/vfsaccessfile_w", S_IFREG | S_IWUSR | S_IWGRP | S_IWOTH},
{VFS_ACCESS_PARENT_DIR "/vfsaccessfile_x", S_IFREG | S_IXUSR | S_IXGRP | S_IXOTH},
{VFS_ACCESS_PARENT_DIR "/vfsaccessfile_rw", S_IFREG | S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH},
{VFS_ACCESS_PARENT_DIR "/vfsaccessdir_r", S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH},
{VFS_ACCESS_PARENT_DIR "/vfsaccessdir_w", S_IFDIR | S_IWUSR | S_IWGRP | S_IWOTH},
{VFS_ACCESS_PARENT_DIR "/vfsaccessdir_x", S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH},
{VFS_ACCESS_PARENT_DIR "/vfsaccessdir_wx", S_IFDIR | S_IWUSR | S_IWGRP | S_IWOTH | S_IXUSR | S_IXGRP | S_IXOTH}};
#define VFS_ACCESS_INODE_ENTRY_FILE "vfsaccessfile"
int g_VfsSetFsUidCaps[] = {
CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_FSETID, CAP_LINUX_IMMUTABLE, CAP_MAC_OVERRIDE, CAP_MKNOD};
static const LXT_VARIATION g_LxtVariations[] = {
{"VfsAccess file object checks", VfsAccessFileObjectChecks},
{"VfsAccess symlinks checks", VfsAccessFileObjectSymlinksChecks},
{"VfsAccess remap reference", VfsAccessRemapReference},
{"VfsAccess chmod", VfsAccessChmod},
{"VfsAccess chmod cap", VfsAccessChmodCap},
{"VfsAccess O_PATH", VfsAccessOPath},
{"VfsAccess sticky bit", VfsAccessStickyBit},
{"VfsAccess set-user-ID set-group-ID", VfsAccessSetUserGroupId},
{"VfsAccess inode checks", VfsAccessInodeChecks},
{"VfsAccess utime cap", VfsAccessUTimeCap},
{"VfsAccess setfsuid", VfsAccessSetFsUid},
//{"VfsAccess Fifo", VfsAccessFifo},
{"VfsAccess set*uid", VfsAccessSetUid}};
bool g_UseDrvFs = false;
int VfsAccessTestEntry(int Argc, char* Argv[])
/*++
--*/
{
LXT_ARGS Args;
int Result;
if ((Argc == 2) && (strcmp(Argv[1], "execvetest") == 0))
{
return VFS_ACCESS_EXECVE_TEST_RESULT;
}
LxtCheckResult(VfsAccessParseArgs(Argc, Argv, &Args));
ErrorExit:
LxtUninitialize();
return !LXT_SUCCESS(Result);
}
void VfsAccessFileObjectCleanup(void)
/*++
Description:
This routine cleans up test files.
Arguments:
None.
Return Value:
None.
--*/
{
int Index;
for (Index = 0; Index < LXT_COUNT_OF(g_VfsSymlinks); Index += 1)
{
unlink(g_VfsSymlinks[Index]);
}
for (Index = 0; Index < LXT_COUNT_OF(g_VfsFiles); ++Index)
{
unlink(g_VfsFiles[Index].Name);
}
for (Index = 0; Index < LXT_COUNT_OF(g_VfsInodeEntries); ++Index)
{
if (S_ISREG(g_VfsInodeEntries[Index].Mode))
{
unlink(g_VfsInodeEntries[Index].Name);
}
else
{
rmdir(g_VfsInodeEntries[Index].Name);
}
}
return;
}
int VfsAccessFileObjectCreateFiles(void)
/*++
--*/
{
unsigned int BytesWritten;
unsigned int Index;
int Fd;
int Result;
Fd = -1;
for (Index = 0; Index < LXT_COUNT_OF(g_VfsFiles); ++Index)
{
unlink(g_VfsFiles[Index].Name);
LxtCheckErrno(Fd = open(g_VfsFiles[Index].Name, O_RDWR | O_CREAT, g_VfsFiles[Index].Mode));
LxtCheckErrno(write(Fd, VFS_FILE_CONTENTS, sizeof(VFS_FILE_CONTENTS)));
if (Index == VFS_ACCESS_REMAP_FILE)
{
BytesWritten = sizeof(VFS_FILE_CONTENTS);
while (BytesWritten < 2 * PAGE_SIZE)
{
LxtCheckErrno(write(Fd, VFS_FILE_CONTENTS, sizeof(VFS_FILE_CONTENTS)));
BytesWritten += sizeof(VFS_FILE_CONTENTS);
}
}
LxtClose(Fd);
Fd = -1;
}
for (Index = 0; Index < LXT_COUNT_OF(g_VfsInodeEntries); ++Index)
{
if (S_ISREG(g_VfsInodeEntries[Index].Mode))
{
unlink(g_VfsInodeEntries[Index].Name);
LxtCheckErrno(Fd = open(g_VfsInodeEntries[Index].Name, O_RDWR | O_CREAT, g_VfsInodeEntries[Index].Mode));
LxtCheckErrno(write(Fd, VFS_FILE_CONTENTS, sizeof(VFS_FILE_CONTENTS)));
LxtClose(Fd);
Fd = -1;
}
else
{
rmdir(g_VfsInodeEntries[Index].Name);
LxtCheckErrno(mkdir(g_VfsInodeEntries[Index].Name, g_VfsInodeEntries[Index].Mode));
}
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (Fd != -1)
{
LxtClose(Fd);
}
return Result;
}
int VfsAccessFileObjectCreateSymlinks(void)
/*++
--*/
{
unsigned int Index;
int IntermediateResult;
int Result;
LxtCheckEqual(LXT_COUNT_OF(g_VfsFiles), LXT_COUNT_OF(g_VfsSymlinks), "%d");
//
// Create symlinks for the files.
//
for (Index = 0; Index < LXT_COUNT_OF(g_VfsFiles); Index += 1)
{
IntermediateResult = symlink(g_VfsFiles[Index].Name, g_VfsSymlinks[Index]);
//
// The symlink call may fail if the symlink already exists. This is ok
// in order to run the unit test on the same machine multiple times.
//
if (IntermediateResult < 0)
{
LxtCheckErrnoFailure(IntermediateResult, EEXIST);
}
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
int VfsAccessCheckResult(int ResultActual, int ResultExpected, int ErrnoActual, int ErrnoExpected, char* Message, int VariationIndex)
/*++
--*/
{
int Result;
Result = LXT_RESULT_FAILURE;
if (ResultActual != ResultExpected)
{
if (ResultActual >= 0)
{
ErrnoActual = 0;
}
LxtLogError("Unexpected %s (%d) result actual %d (%s) != expected %d", Message, VariationIndex, ResultActual, strerror(ErrnoActual), ResultExpected);
goto ErrorExit;
}
if ((ResultActual == -1) && (ErrnoActual != ErrnoExpected))
{
Result = LXT_RESULT_FAILURE;
LxtLogError("Unexpected %s (%d) errno actual %s != expected %s", Message, VariationIndex, strerror(ErrnoActual), strerror(ErrnoExpected));
goto ErrorExit;
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
int VfsAccessFifo(PLXT_ARGS Args)
/*++
Description:
This routine tests access permissions on fifos.
Arguments:
Args - Supplies the command line arguments.
Return Value:
Returns 0 on success, -1 on failure.
--*/
{
int ChildPid;
int Result;
LxtCheckErrnoZeroSuccess(mkfifo(VFS_ACCESS_FIFO, 0600));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
setgid(1000);
setuid(1000);
LxtCheckErrnoFailure(open(VFS_ACCESS_FIFO, O_RDONLY | O_NONBLOCK), EACCES);
LxtCheckErrnoFailure(open(VFS_ACCESS_FIFO, O_WRONLY | O_NONBLOCK), EACCES);
exit(0);
}
LxtWaitPidPoll(ChildPid, 0);
ErrorExit:
unlink(VFS_ACCESS_FIFO);
return Result;
}
int VfsAccessFileObjectChecks(PLXT_ARGS Args)
/*++
--*/
{
int AccessMode;
char Buffer;
int ErrnoExpected;
VFS_ACCESS_FILE_OBJECT Files[VFS_FILE_OBJECT_COUNT];
unsigned int Index;
void* Map;
int Result;
int ResultActual;
int ResultExpected;
bool VirtiofsNoDax;
LxtLogInfo("Fs type %d with dax = %d\n", g_LxtFsInfo.FsType, g_LxtFsInfo.Flags.Dax);
VirtiofsNoDax = g_LxtFsInfo.FsType == LxtFsTypeVirtioFs && g_LxtFsInfo.Flags.Dax == 0;
memset(Files, -1, sizeof(Files));
LxtCheckResult(VfsAccessFileObjectOpenFiles(Files));
for (Index = 0; Index < VFS_FILE_OBJECT_COUNT; ++Index)
{
//
// Validate read with a valid buffer.
//
ResultExpected = -1;
if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 1;
}
ResultActual = LxtRead(Files[Index].Fd, &Buffer, 1);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "read", Index));
//
// Validate read with a invalid buffer and a size of 0. The size of 0
// should cause the buffer to not be checked.
//
ResultExpected = -1;
if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 0;
}
ResultActual = LxtRead(Files[Index].Fd, (void*)0x1, 0);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "read", Index));
//
// Validate write with a valid buffer.
//
ResultExpected = -1;
if ((Files[Index].Flags == O_WRONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 1;
}
ResultActual = LxtWrite(Files[Index].Fd, &Buffer, 1);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "write", Index));
//
// Validate write with a invalid buffer and a size of 0. The size of 0
// should cause the buffer to not be checked.
//
ResultExpected = -1;
if ((Files[Index].Flags == O_WRONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 0;
}
ResultActual = LxtWrite(Files[Index].Fd, (void*)0x1, 0);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "write", Index));
//
// Validate map read shared and upgrading to write access.
//
// N.B. The Linux 9p client does not allow mapping shared if the file is
// opened for write.
//
// N.B. The virtiofs device relies on fuse mapping which only supports
// shared in the presence of DAX.
//
ErrnoExpected = EACCES;
if (Files[Index].Flags == O_PATH)
{
ErrnoExpected = EBADF;
}
else if (VirtiofsNoDax && (Files[Index].Flags == O_RDONLY || (Files[Index].Flags & O_ACCMODE) == O_RDWR))
{
ErrnoExpected = ENODEV;
}
ResultExpected = -1;
if (!VirtiofsNoDax && (((Files[Index].Flags & O_ACCMODE) == O_RDONLY) || ((Files[Index].Flags & O_ACCMODE) == O_RDWR)) &&
((Files[Index].Flags & O_PATH) == 0))
{
ResultExpected = 1;
}
Map = mmap(NULL, sizeof(Buffer), PROT_READ, MAP_SHARED, Files[Index].Fd, 0);
ResultActual = -1;
if (Map != MAP_FAILED)
{
ResultActual = 1;
}
LxtLogInfo("%d, %d, %d", Index, Files[Index].Flags, ResultExpected);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read shared", Index));
if (Map != MAP_FAILED)
{
ResultExpected = -1;
if ((Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 0;
}
ResultActual = mprotect(Map, sizeof(Buffer), PROT_WRITE);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read shared mprotect", Index));
LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer)));
}
//
// Validate map read private and upgrading to write access.
//
// N.B. The Linux 9p client does not allow mapping shared if the file is
// opened for write.
//
ResultExpected = -1;
if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 1;
}
Map = mmap(NULL, sizeof(Buffer), PROT_READ, MAP_PRIVATE, Files[Index].Fd, 0);
ResultActual = -1;
if (Map != MAP_FAILED)
{
ResultActual = 1;
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read private", Index));
if (Map != MAP_FAILED)
{
ResultExpected = -1;
if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 0;
}
ResultActual = mprotect(Map, sizeof(Buffer), PROT_WRITE);
VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read private mprotect", Index);
LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer)));
}
//
// Validate map write shared and private
//
// N.B. The virtiofs device relies on fuse mapping which only supports
// shared in the presence of DAX.
//
ErrnoExpected = EACCES;
if (Files[Index].Flags == O_PATH)
{
ErrnoExpected = EBADF;
}
else if (VirtiofsNoDax && (Files[Index].Flags & O_ACCMODE) == O_RDWR)
{
ErrnoExpected = ENODEV;
}
ResultExpected = -1;
if (!VirtiofsNoDax && (Files[Index].Flags & O_ACCMODE) == O_RDWR)
{
ResultExpected = 1;
}
Map = mmap(NULL, sizeof(Buffer), PROT_WRITE, MAP_SHARED, Files[Index].Fd, 0);
ResultActual = -1;
if (Map != MAP_FAILED)
{
ResultActual = 1;
LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer)));
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap write shared", Index));
ResultExpected = -1;
if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 1;
}
Map = mmap(NULL, sizeof(Buffer), PROT_WRITE, MAP_PRIVATE, Files[Index].Fd, 0);
ResultActual = -1;
if (Map != MAP_FAILED)
{
ResultActual = 1;
LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer)));
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap write private", Index));
LxtClose(Files[Index].Fd);
Files[Index].Fd = -1;
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
int VfsAccessFileObjectSymlinksChecks(PLXT_ARGS Args)
/*++
--*/
{
char Buffer;
int ErrnoExpected;
VFS_ACCESS_FILE_OBJECT Files[VFS_FILE_OBJECT_COUNT];
unsigned int Index;
void* Map;
int Result;
int ResultActual;
int ResultExpected;
bool VirtiofsNoDax;
LxtLogInfo("Fs type %d with dax = %d\n", g_LxtFsInfo.FsType, g_LxtFsInfo.Flags.Dax);
VirtiofsNoDax = g_LxtFsInfo.FsType == LxtFsTypeVirtioFs && g_LxtFsInfo.Flags.Dax == 0;
memset(Files, -1, sizeof(Files));
LxtCheckResult(VfsAccessFileObjectOpenSymlinks(Files));
for (Index = 0; Index < VFS_FILE_OBJECT_COUNT; ++Index)
{
//
// Validate read
//
ResultExpected = -1;
if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 1;
}
ResultActual = LxtRead(Files[Index].Fd, &Buffer, 1);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "read", Index));
//
// Validate write
//
ResultExpected = -1;
if ((Files[Index].Flags == O_WRONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND)))
{
ResultExpected = 1;
}
ResultActual = LxtWrite(Files[Index].Fd, &Buffer, 1);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "write", Index));
//
// Validate map read
//
ErrnoExpected = EACCES;
if (Files[Index].Flags == O_PATH)
{
ErrnoExpected = EBADF;
}
else if (VirtiofsNoDax && (Files[Index].Flags == O_RDONLY || (Files[Index].Flags & O_ACCMODE) == O_RDWR))
{
ErrnoExpected = ENODEV;
}
ResultExpected = -1;
if (!VirtiofsNoDax && (((Files[Index].Flags & O_ACCMODE) == O_RDONLY) || ((Files[Index].Flags & O_ACCMODE) == O_RDWR)) &&
((Files[Index].Flags & O_PATH) == 0))
{
ResultExpected = 1;
}
Map = mmap(NULL, sizeof(Buffer), PROT_READ, MAP_SHARED, Files[Index].Fd, 0);
ResultActual = -1;
if (Map != MAP_FAILED)
{
ResultActual = 1;
LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer)));
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read", Index));
//
// Validate map write
//
ErrnoExpected = EACCES;
if (Files[Index].Flags == O_PATH)
{
ErrnoExpected = EBADF;
}
else if (VirtiofsNoDax && (Files[Index].Flags & O_ACCMODE) == O_RDWR)
{
ErrnoExpected = ENODEV;
}
ResultExpected = -1;
if (!VirtiofsNoDax && (Files[Index].Flags & O_ACCMODE) == O_RDWR)
{
ResultExpected = 1;
}
Map = mmap(NULL, sizeof(Buffer), PROT_WRITE, MAP_SHARED, Files[Index].Fd, 0);
ResultActual = -1;
if (Map != MAP_FAILED)
{
ResultActual = 1;
LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer)));
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap write", Index));
LxtClose(Files[Index].Fd);
Files[Index].Fd = -1;
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
int VfsAccessFileObjectOpenFiles(VFS_ACCESS_FILE_OBJECT Files[])
/*++
--*/
{
unsigned int Index;
int Result;
for (Index = 0; Index < VFS_FILE_OBJECT_COUNT; ++Index)
{
Files[Index].Flags = g_VfsFileObjectFlags[Index];
LxtCheckErrno(Files[Index].Fd = open(g_VfsFiles[VFS_ACCESS_FILE_OBJECT_FILE].Name, Files[Index].Flags, 0));
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
int VfsAccessFileObjectOpenSymlinks(VFS_ACCESS_FILE_OBJECT Files[])
/*++
--*/
{
unsigned int Index;
int Result;
for (Index = 0; Index < VFS_FILE_OBJECT_COUNT; Index += 1)
{
Files[Index].Flags = g_VfsFileObjectFlags[Index];
LxtCheckErrno(Files[Index].Fd = open(g_VfsSymlinks[VFS_ACCESS_FILE_OBJECT_FILE], Files[Index].Flags, 0));
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
int VfsAccessRemapReference(PLXT_ARGS Args)
{
char Buffer;
int FdReadOnly = -1;
int FdReadWrite = -1;
int MapFlags;
void* MapResult;
void* MapReadOnly = NULL;
void* MapReadWrite = NULL;
void* RemappedMemory = NULL;
int Result;
bool VirtiofsNoDax;
VirtiofsNoDax = g_LxtFsInfo.FsType == LxtFsTypeVirtioFs && g_LxtFsInfo.Flags.Dax == 0;
MapFlags = VirtiofsNoDax ? MAP_PRIVATE : MAP_SHARED;
//
// Open and map a file whose only reference is read only and open second
// file descriptor and mapping read write.
//
LxtCheckErrno(FdReadOnly = open(g_VfsFiles[VFS_ACCESS_REMAP_FILE].Name, O_RDONLY, 0));
LxtCheckMapErrno(MapReadOnly = mmap(NULL, sizeof(Buffer), PROT_READ, MapFlags, FdReadOnly, 0));
LxtCheckErrno(FdReadWrite = open(g_VfsFiles[VFS_ACCESS_REMAP_FILE].Name, O_RDWR, 0));
LxtCheckMapErrno(MapReadWrite = mmap(NULL, sizeof(Buffer), PROT_READ | PROT_WRITE, MapFlags, FdReadWrite, 0));
LxtCheckMapErrno(RemappedMemory = mremap(MapReadWrite, sizeof(Buffer), PAGE_SIZE * 2, MREMAP_MAYMOVE));
ErrorExit:
if (FdReadOnly != -1)
{
if (MapReadOnly != NULL)
{
LxtMunmap(MapReadOnly, sizeof(Buffer));
}
LxtClose(FdReadOnly);
}
if (FdReadWrite != -1)
{
if (RemappedMemory != NULL)
{
LxtMunmap(RemappedMemory, PAGE_SIZE * 2);
}
else if (MapReadWrite != NULL)
{
LxtMunmap(MapReadWrite, sizeof(Buffer));
}
LxtClose(FdReadWrite);
}
return Result;
}
int VfsAccessChmod(PLXT_ARGS Args)
/*++
--*/
{
int DirFd;
int Result;
struct stat StatBuf;
DirFd = -1;
//
// Set bits with chmod and then fchmod.
//
rmdir(VFS_ACCESS_CHMOD_DIR);
LxtCheckErrno(mkdir(VFS_ACCESS_CHMOD_DIR, S_IRWXU));
LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf));
if (StatBuf.st_mode != (S_IRWXU | S_IFDIR))
{
LxtLogError("Unexpected mode %d != S_IRWXU | S_IFDIR", StatBuf.st_mode);
goto ErrorExit;
}
LxtCheckErrno(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXG));
LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf));
if (StatBuf.st_mode != (S_IRWXG | S_IFDIR))
{
LxtLogError("Unexpected mode %d != S_IRWXG | S_IFDIR", StatBuf.st_mode);
goto ErrorExit;
}
LxtCheckErrno(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXO));
LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf));
if (StatBuf.st_mode != (S_IRWXO | S_IFDIR))
{
LxtLogError("Unexpected mode %d != S_IRWXO | S_IFDIR", StatBuf.st_mode);
goto ErrorExit;
}
LxtCheckErrno(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXU));
LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf));
if (StatBuf.st_mode != (S_IRWXU | S_IFDIR))
{
LxtLogError("Unexpected mode %d != S_IRWXU | S_IFDIR", StatBuf.st_mode);
goto ErrorExit;
}
LxtCheckErrno(chmod(VFS_ACCESS_CHMOD_DIR, 0xffff));
LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf));
if (StatBuf.st_mode != (S_ISVTX | S_ISGID | S_ISUID | S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR))
{
LxtLogError("Unexpected mode %d != All bits", StatBuf.st_mode);
goto ErrorExit;
}
LxtCheckErrno(DirFd = open(VFS_ACCESS_CHMOD_DIR, O_DIRECTORY | O_RDONLY, 0));
LxtCheckErrno(fchmod(DirFd, S_IRWXG));
LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf));
if (StatBuf.st_mode != (S_IRWXG | S_IFDIR))
{
LxtLogError("Unexpected mode %d != S_IRWXG | S_IFDIR", StatBuf.st_mode);
goto ErrorExit;
}
LxtCheckErrno(fchmod(DirFd, S_IRWXO));
LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf));
if (StatBuf.st_mode != (S_IRWXO | S_IFDIR))
{
LxtLogError("Unexpected mode %d != S_IRWXO | S_IFDIR", StatBuf.st_mode);
goto ErrorExit;
}
LxtCheckErrno(fchmod(DirFd, S_IRWXU));
LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf));
if (StatBuf.st_mode != (S_IRWXU | S_IFDIR))
{
LxtLogError("Unexpected mode %d != S_IRWXU | S_IFDIR", StatBuf.st_mode);
goto ErrorExit;
}
LxtCheckErrno(fchmod(DirFd, 0xffff));
LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf));
if (StatBuf.st_mode != (S_ISVTX | S_ISGID | S_ISUID | S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR))
{
LxtLogError("Unexpected mode %d != All bits", StatBuf.st_mode);
goto ErrorExit;
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (DirFd != -1)
{
LxtClose(DirFd);
}
rmdir(VFS_ACCESS_CHMOD_DIR);
return Result;
}
void VfsAccessChmodCapChild(void)
/*++
--*/
{
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
int Result;
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
CapData[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE);
CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN);
CapData[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID);
CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID);
CapData[0].effective = CapData[0].permitted;
CapData[1].effective = CapData[1].permitted;
//
// Drop privileges so the current process does not have CAP_FOWNER.
//
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
LxtCheckErrno(setgid(VFS_ACCESS_UID));
LxtCheckErrno(setuid(VFS_ACCESS_UID));
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
//
// TODO: Update the below when checks are enforced
//
//
// Try to chmod the directory to the current value.
//
LxtCheckErrnoFailure(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXU), EPERM);
//
// Try to chmod on the directory without CAP_FOWNER to 0751.
//
LxtCheckErrnoFailure(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXU | S_IRGRP | S_IXGRP | S_IXOTH), EPERM);
Result = LXT_RESULT_SUCCESS;
ErrorExit:
_exit(Result);
}
int VfsAccessChmodCap(PLXT_ARGS Args)
/*++
--*/
{
int ChildPid;
int Result;
rmdir(VFS_ACCESS_CHMOD_DIR);
LxtCheckErrno(mkdir(VFS_ACCESS_CHMOD_DIR, S_IRWXU));
ChildPid = fork();
if (ChildPid == 0)
{
VfsAccessChmodCapChild();
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
Result = LXT_RESULT_SUCCESS;
ErrorExit:
rmdir(VFS_ACCESS_CHMOD_DIR);
return Result;
}
void VfsAccessOPathChild(void)
/*++
--*/
{
char Buffer[100];
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
int Fd;
int Result;
struct stat StatBuffer;
struct statfs StatFsBuffer;
struct timespec Times[2] = {{0, UTIME_NOW}, {0, UTIME_NOW}};
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
CapData[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID);
CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID);
CapData[0].effective = CapData[0].permitted;
CapData[1].effective = CapData[1].permitted;
Fd = -1;
//
// Drop privileges so the current process does not have VFS related
// capabilities.
//
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
//
// Open the file with O_PATH and check the behavior for the syscalls of
// interest.
//
LxtCheckErrno(Fd = open(VFS_ACCESS_OPATH_FILE, O_PATH, 0));
//
// Check syscalls that take a file descriptor should fail because O_PATH was
// specified.
//
LxtCheckErrnoFailure(fchmod(Fd, 0), EBADF);
LxtCheckErrnoFailure(fchown(Fd, 0, 0), EBADF);
LxtCheckErrnoFailure(fsync(Fd), EBADF);
LxtCheckErrnoFailure(LxtGetdents64(Fd, (struct dirent*)Buffer, sizeof(Buffer)), EBADF);
LxtCheckErrnoFailure(futimens(Fd, Times), EBADF);
LxtCheckErrnoFailure(flistxattr(Fd, Buffer, sizeof(Buffer)), EBADF);
//
// Check syscalls that take a file descriptor should succeed even through
// O_PATH was specified.
//
LxtCheckErrno(fstat(Fd, &StatBuffer));
LxtCheckErrno(fstatfs(Fd, &StatFsBuffer));
//
// Check syscalls that should succeed on a directory with O_PATH specified.
//
LxtCheckErrnoZeroSuccess(close(Fd));
LxtCheckErrno(Fd = open(VFS_ACCESS_OPATH_DIR, O_PATH | O_DIRECTORY));
LxtCheckErrnoZeroSuccess(fchdir(Fd));
//
// Chdir should still fail if execute permissions are removed.
//
LxtCheckErrnoZeroSuccess(chmod(VFS_ACCESS_OPATH_DIR, 0));
LxtCheckErrnoFailure(fchdir(Fd), EACCES);
//
// Check syscalls that take a path should succeed because they do not
// require access to the file, but instead just the path.
//
LxtCheckErrno(chmod(VFS_ACCESS_OPATH_FILE, 0));
LxtCheckErrno(chown(VFS_ACCESS_OPATH_FILE, 0, 0));
LxtCheckErrno(stat(VFS_ACCESS_OPATH_FILE, &StatBuffer));
LxtCheckErrno(statfs(VFS_ACCESS_OPATH_FILE, &StatFsBuffer));
LxtCheckErrno(readlink(VFS_ACCESS_OPATH_FILE_LINK, Buffer, sizeof(Buffer)));
//
// Xattr is not supported on drvfs currently.
//
if (g_UseDrvFs == false)
{
LxtCheckErrno(listxattr(VFS_ACCESS_OPATH_FILE_LINK, Buffer, sizeof(Buffer)));
LxtCheckErrno(llistxattr(VFS_ACCESS_OPATH_FILE_LINK, Buffer, sizeof(Buffer)));
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (Fd != -1)
{
LxtClose(Fd);
}
_exit(Result);
}
int VfsAccessOPath(PLXT_ARGS Args)
/*++
--*/
{
int ChildPid;
int Fd;
int Result;
unlink(VFS_ACCESS_OPATH_FILE);
unlink(VFS_ACCESS_OPATH_FILE_LINK);
LxtCheckErrno(mkdir(VFS_ACCESS_OPATH_DIR, 0111));
LxtCheckErrno(Fd = open(VFS_ACCESS_OPATH_FILE, O_CREAT, 0));
LxtCheckErrno(symlink(VFS_ACCESS_OPATH_FILE, VFS_ACCESS_OPATH_FILE_LINK));
ChildPid = fork();
if (ChildPid == 0)
{
VfsAccessOPathChild();
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (Fd != -1)
{
LxtClose(Fd);
}
unlink(VFS_ACCESS_OPATH_FILE);
unlink(VFS_ACCESS_OPATH_FILE_LINK);
rmdir(VFS_ACCESS_OPATH_DIR);
return Result;
}
void VfsAccessRenameCapChild(void)
/*++
--*/
{
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
int File;
int Result;
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
CapData[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE);
CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN);
CapData[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID);
CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID);
CapData[0].effective = CapData[0].permitted;
CapData[1].effective = CapData[1].permitted;
//
// Drop privileges so the current process does not have CAP_FOWNER.
//
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
LxtCheckErrno(setgid(VFS_ACCESS_UID));
LxtCheckErrno(setuid(VFS_ACCESS_UID));
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
//
// Create a file and directory for the current user.
//
LxtCheckErrno(mkdir(VFS_ACCESS_STICKY_BIT_DIR "/userdir1", S_IRWXU));
LxtCheckErrno(File = creat(VFS_ACCESS_STICKY_BIT_DIR "/userfile1", S_IRWXU));
close(File);
//
// Try to rename the file and directory to an existing entry without
// CAP_FOWNER.
//
LxtCheckErrnoFailure(rename(VFS_ACCESS_STICKY_BIT_DIR "/userfile1", VFS_ACCESS_STICKY_BIT_DIR "/file1"), EPERM);
LxtCheckErrnoFailure(rename(VFS_ACCESS_STICKY_BIT_DIR "/userdir1", VFS_ACCESS_STICKY_BIT_DIR "/dir1"), EPERM);
Result = LXT_RESULT_SUCCESS;
ErrorExit:
rmdir(VFS_ACCESS_STICKY_BIT_DIR "/userdir1");
remove(VFS_ACCESS_STICKY_BIT_DIR "/userfile1");
_exit(Result);
}
void VfsAccessRmdirCapChild(void)
/*++
--*/
{
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
int Result;
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
CapData[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE);
CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN);
CapData[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID);
CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID);
CapData[0].effective = CapData[0].permitted;
CapData[1].effective = CapData[1].permitted;
//
// Drop privileges so the current process does not have CAP_FOWNER.
//
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
LxtCheckErrno(setgid(VFS_ACCESS_UID));
LxtCheckErrno(setuid(VFS_ACCESS_UID));
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
//
// Try to remove the file and directory without CAP_FOWNER.
//
LxtCheckErrnoFailure(remove(VFS_ACCESS_STICKY_BIT_DIR "/file1"), EPERM);
LxtCheckErrnoFailure(remove(VFS_ACCESS_STICKY_BIT_DIR "/dir1"), EPERM);
Result = LXT_RESULT_SUCCESS;
ErrorExit:
_exit(Result);
}
int VfsAccessStickyBit(PLXT_ARGS Args)
/*++
--*/
{
int ChildPid;
int ChildStatus;
int File = 0;
int Result;
//
// Create a directory with the sticky bit set and a file inside.
//
LxtCheckErrno(mkdir(VFS_ACCESS_STICKY_BIT_DIR, S_IRWXU | S_ISVTX));
LxtCheckErrno(mkdir(VFS_ACCESS_STICKY_BIT_DIR "/dir1", S_IRWXU));
LxtCheckErrno(File = creat(VFS_ACCESS_STICKY_BIT_DIR "/file1", S_IRWXU));
ChildPid = fork();
if (ChildPid == 0)
{
VfsAccessRmdirCapChild();
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
ChildPid = fork();
if (ChildPid == 0)
{
VfsAccessRenameCapChild();
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (File > 0)
{
close(File);
}
rmdir(VFS_ACCESS_STICKY_BIT_DIR "/dir1");
remove(VFS_ACCESS_STICKY_BIT_DIR "/file1");
rmdir(VFS_ACCESS_STICKY_BIT_DIR);
return Result;
}
int VfsAccessSetUserGroupIdExecveChild(void)
/*++
Routine Description:
This routine runs the child process for VfsAccessSetUserGroupId.
Arguments:
None.
Return Value:
Returns 0 on success, -1 on failure.
--*/
{
uid_t EffectiveGroup;
uid_t FilesystemGroup;
uid_t RealGroup;
uid_t SavedGroup;
int Result = LXT_RESULT_FAILURE;
uid_t EffectiveUser;
uid_t FilesystemUser;
uid_t RealUser;
uid_t SavedUser;
LxtLogInfo("Child executable starting");
//
// Get the user and group id and verify they match the expected.
//
LxtCheckResult(getresuid(&RealUser, &EffectiveUser, &SavedUser));
LxtCheckEqual(EffectiveUser, VFS_ACCESS_UID, "%u");
LxtCheckEqual(SavedUser, VFS_ACCESS_UID, "%u");
FilesystemUser = LxtSetfsuid(-1);
LxtCheckEqual(FilesystemUser, VFS_ACCESS_UID, "%u");
LxtCheckResult(getresgid(&RealGroup, &EffectiveGroup, &SavedGroup));
LxtCheckEqual(EffectiveGroup, VFS_ACCESS_UID, "%u");
LxtCheckEqual(SavedGroup, VFS_ACCESS_UID, "%u");
FilesystemGroup = LxtSetfsgid(-1);
LxtCheckEqual(FilesystemGroup, VFS_ACCESS_UID, "%u");
LxtLogInfo("Child executable finished");
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
void VfsAccessSetUserGroupIdFSetIdChild(int Fd1)
/*++
--*/
{
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
char* Data = "Test data";
int Fd2 = -1;
char Path[PATH_MAX];
int Result;
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
//
// Drop privileges so the current process does not have VFS capabilities
// and is in the other user\group.
//
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
LxtCheckErrno(setgid(VFS_ACCESS_UID));
LxtCheckErrno(setuid(VFS_ACCESS_UID));
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
// The plan 9 server cannot know about the uid change after open, so reopen
// the file with the new security context.
if (g_LxtFsInfo.FsType == LxtFsTypePlan9)
{
snprintf(Path, sizeof(Path), "/proc/self/fd/%d", Fd1);
LxtCheckResult(Fd2 = open(Path, O_WRONLY));
LxtCheckErrno(write(Fd2, Data, 1));
LxtCheckClose(Fd2);
}
else
{
LxtCheckErrno(write(Fd1, Data, 1));
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (Fd2 >= 0)
{
close(Fd2);
}
_exit(Result);
}
void VfsAccessSetUserGroupIdChmodChild(char* FilePath, uid_t Uid, gid_t Gid)
/*++
--*/
{
struct stat Buffer;
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
int Result;
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN);
// CapData[CAP_TO_INDEX(CAP_FOWNER)].permitted |= CAP_TO_MASK(CAP_FOWNER);
CapData[0].effective = CapData[0].permitted;
CapData[1].effective = CapData[1].permitted;
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
LxtLogInfo("chown(%s, %d, %d)", FilePath, Uid, Gid);
LxtCheckErrnoFailure(chown(FilePath, Uid, Gid), EPERM);
LxtCheckErrno(stat(FilePath, &Buffer));
LxtCheckEqual((Buffer.st_mode & (S_ISUID | S_ISGID)), (S_ISUID | S_ISGID), "%o");
Result = LXT_RESULT_SUCCESS;
ErrorExit:
_exit(Result);
}
int VfsAccessSetUserGroupId(PLXT_ARGS Args)
/*++
--*/
{
char* Argv[4];
struct stat Buffer;
int ChildPid;
char* Envp[1];
mode_t ExpectedMode;
int Fd1 = 0;
int Fd2 = 0;
int Mode;
int Result;
rmdir(VFS_ACCESS_GROUP_USER_ID_DIR);
//
// Create a directory with the set-group-ID bit set.
//
LxtCheckErrno(mkdir(VFS_ACCESS_GROUP_USER_ID_DIR, S_IRWXU));
LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR, &Buffer));
LxtLogInfo("VFS_ACCESS_GROUP_USER_ID_DIR mode after mkdir %o", Buffer.st_mode);
//
// Change the owner of the directory and set the set-group-ID bit.
//
LxtCheckErrno(chown(VFS_ACCESS_GROUP_USER_ID_DIR, VFS_ACCESS_UID, VFS_ACCESS_UID));
LxtCheckErrno(chmod(VFS_ACCESS_GROUP_USER_ID_DIR, S_IRWXU | S_ISGID));
LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR, &Buffer));
LxtLogInfo("VFS_ACCESS_GROUP_USER_ID_DIR mode after chmod %o", Buffer.st_mode);
//
// Create some files and child directories.
//
LxtCheckErrno(Fd1 = creat(VFS_ACCESS_GROUP_USER_ID_DIR "/file1", 0777 | S_IRWXU | S_ISGID | S_ISUID));
LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/file1", &Buffer));
LxtLogInfo("VFS_ACCESS_GROUP_USER_ID_DIR /file1 mode after mkdir %o", Buffer.st_mode);
LxtCheckErrno(mkdir(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1", S_IRWXU));
LxtCheckErrno(Fd2 = creat(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/file2", S_IRWXU));
LxtCheckErrno(mkdir(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/dir2", S_IRWXU));
//
// Validate the files and directories have the correct uid and gid.
//
LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/file1", &Buffer));
if (Buffer.st_gid != VFS_ACCESS_UID)
{
LxtLogError("/file1 gid %u does not match expected %u", Buffer.st_gid, VFS_ACCESS_UID);
}
LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1", &Buffer));
if (Buffer.st_gid != VFS_ACCESS_UID)
{
LxtLogError("/dir gid %u does not match expected %u", Buffer.st_gid, VFS_ACCESS_UID);
}
LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/file2", &Buffer));
if (Buffer.st_gid != VFS_ACCESS_UID)
{
LxtLogError("/dir1/file2 gid %u does not match expected %u", Buffer.st_gid, VFS_ACCESS_UID);
}
LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/dir2", &Buffer));
if (Buffer.st_gid != VFS_ACCESS_UID)
{
LxtLogError("/dir1/dir2 gid %u does not match expected %u", Buffer.st_gid, VFS_ACCESS_UID);
}
//
// Validate the execute behavior of the set user id and group id bits.
// Make a copy of the current binary to use for the test.
//
// change Args->Argv[0] so that it points to the new single test binary design
Args->Argv[0] = WSL_UNIT_TEST_BINARY;
LxtCheckResult(LxtCopyFile(Args->Argv[0], VFS_ACCESS_PARENT_DIR "/wsl_unit_tests"));
LxtCheckErrno(chown(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", VFS_ACCESS_UID, VFS_ACCESS_UID));
LxtCheckErrno(stat(Args->Argv[0], &Buffer));
LxtCheckErrno(chmod(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", (Buffer.st_mode | S_ISUID | S_ISGID)));
ExpectedMode = Buffer.st_mode;
//
// Start the child process.
//
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
Argv[0] = VFS_ACCESS_PARENT_DIR "/wsl_unit_tests";
Argv[1] = "vfsaccess";
Argv[2] = "-c";
Argv[3] = Envp[0] = NULL;
LxtCheckErrno(stat(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", &Buffer));
LxtLogInfo("child %o %u %u", Buffer.st_mode, Buffer.st_uid, Buffer.st_gid);
execve(Argv[0], Argv, Envp);
LxtLogError("Execve failed, errno: %d (%s)", errno, strerror(errno));
_exit(LXT_RESULT_FAILURE);
}
//
// Wait for the child to exit.
//
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
//
// Set the uid and gid again to make sure the set-user-id and set-group-id
// bits are stripped from the mode.
//
LxtCheckErrno(chown(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", -1, -1));
LxtCheckErrno(stat(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", &Buffer));
LxtCheckEqual(Buffer.st_mode, ExpectedMode, "0%o");
//
// Re-set the set-user-id and set-group-id bits.
//
LxtCheckErrno(chmod(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", (Buffer.st_mode | S_ISUID | S_ISGID)));
// VirtioFs does not currently handle capability flags. There is a new KILLPRIV2 FUSE flag that may address this in the future.
if (g_LxtFsInfo.FsType != LxtFsTypeVirtioFs)
{
//
// Fork and drop privileges so the current process does not have CAP_FOWNER
// which is required for changing the owner of a file with the set-user-id
// or set-group-id bits set.
//
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
VfsAccessSetUserGroupIdChmodChild(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", VFS_ACCESS_UID, VFS_ACCESS_UID);
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
VfsAccessSetUserGroupIdChmodChild(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", VFS_ACCESS_UID, -1);
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
VfsAccessSetUserGroupIdChmodChild(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", -1, VFS_ACCESS_UID);
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
VfsAccessSetUserGroupIdChmodChild(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", -1, -1);
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
}
//
// Validate the behavior of CAP_FSETID for files.
//
LxtLogInfo("Checking CAP_FSETID for files");
LxtCheckErrno(fstat(Fd1, &Buffer));
Mode = Buffer.st_mode;
if ((Mode & (S_ISGID | S_ISUID)) != (S_ISGID | S_ISUID))
{
LxtLogError("Unexpected mode");
Result = LXT_RESULT_FAILURE;
goto ErrorExit;
}
LxtCheckErrno(write(Fd1, &Buffer, sizeof(Buffer)));
LxtCheckErrno(fstat(Fd1, &Buffer));
LxtCheckEqual(Buffer.st_mode, Mode, "%o");
LxtCheckErrno(ChildPid = fork());
if (ChildPid == 0)
{
VfsAccessSetUserGroupIdFSetIdChild(Fd1);
}
//
// Wait for the child to exit and validate that the set id bits were
// silently removed.
//
LxtCheckResult(LxtWaitPidPoll(ChildPid, 0));
LxtCheckErrno(fstat(Fd1, &Buffer));
LxtCheckEqual(Buffer.st_mode, Mode & ~(S_ISGID | S_ISUID), "%o");
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (Fd1 > 0)
{
LxtClose(Fd1);
}
if (Fd2 > 0)
{
LxtClose(Fd2);
}
if (ChildPid == 0)
{
_exit(Result);
}
//
// Clean-up created files and directories.
//
unlink(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests");
remove(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/file2");
rmdir(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/dir2");
remove(VFS_ACCESS_GROUP_USER_ID_DIR "/file1");
rmdir(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1");
rmdir(VFS_ACCESS_GROUP_USER_ID_DIR);
return Result;
}
void VfsAccessInodeChecksChild(void)
/*++
--*/
{
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
char* CommandLine[] = {NULL, NULL};
int ErrnoExpected;
char FileName[100];
unsigned int Index;
int Result;
int ResultActual;
int ResultExpected;
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
//
// Drop privileges so the current process does not have VFS capabilities
// and is in the other user\group.
//
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
LxtCheckErrno(setgid(VFS_ACCESS_UID));
LxtCheckErrno(setuid(VFS_ACCESS_UID));
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
//
// For each file, check that read, write and execute is enforced. Similarly
// for directories check that list, create\delete, and search is enforced.
//
for (Index = 0; Index < LXT_COUNT_OF(g_VfsInodeEntries); ++Index)
{
if (S_ISREG(g_VfsInodeEntries[Index].Mode))
{
//
// Check read access.
//
ResultExpected = -1;
if ((g_VfsInodeEntries[Index].Mode & S_IROTH) != 0)
{
ResultExpected = 0;
}
ResultActual = open(g_VfsInodeEntries[Index].Name, O_RDONLY, 0);
if (ResultActual != -1)
{
LxtClose(ResultActual);
ResultActual = 0;
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "file open O_RDONLY", Index));
//
// Check write access.
//
ResultExpected = -1;
if ((g_VfsInodeEntries[Index].Mode & S_IWOTH) != 0)
{
ResultExpected = 0;
}
ResultActual = open(g_VfsInodeEntries[Index].Name, O_WRONLY, 0);
if (ResultActual != -1)
{
LxtClose(ResultActual);
ResultActual = 0;
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "file open O_WRONLY", Index));
//
// Check read\write access.
//
ResultExpected = -1;
if (((g_VfsInodeEntries[Index].Mode & S_IROTH) != 0) && ((g_VfsInodeEntries[Index].Mode & S_IWOTH) != 0))
{
ResultExpected = 0;
}
ResultActual = open(g_VfsInodeEntries[Index].Name, O_RDWR, 0);
if (ResultActual != -1)
{
LxtClose(ResultActual);
ResultActual = 0;
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "file open O_RDWR", Index));
//
// Check no access (open time check for read\write).
//
if (((g_VfsInodeEntries[Index].Mode & S_IROTH) != 0) && ((g_VfsInodeEntries[Index].Mode & S_IWOTH) != 0))
{
ResultExpected = 0;
}
ResultActual = open(g_VfsInodeEntries[Index].Name, O_NOACCESS, 0);
if (ResultActual != -1)
{
LxtClose(ResultActual);
ResultActual = 0;
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "file open O_NOACCESS", Index));
//
// Check execute access.
//
ErrnoExpected = EACCES;
ResultExpected = -1;
if ((g_VfsInodeEntries[Index].Mode & S_IXOTH) != 0)
{
ErrnoExpected = ENOEXEC;
}
CommandLine[0] = g_VfsInodeEntries[Index].Name;
ResultActual = execv(CommandLine[0], CommandLine);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "execv", Index));
}
else
{
//
// Check read access.
//
ResultExpected = -1;
if ((g_VfsInodeEntries[Index].Mode & S_IROTH) != 0)
{
ResultExpected = 0;
}
ResultActual = open(g_VfsInodeEntries[Index].Name, O_RDONLY, 0);
if (ResultActual != -1)
{
LxtClose(ResultActual);
ResultActual = 0;
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "directory open O_RDONLY", Index));
//
// Check create\delete (write) access. Execute access is also
// required to create and delete.
//
sprintf(FileName, "%s/%s", g_VfsInodeEntries[Index].Name, VFS_ACCESS_INODE_ENTRY_FILE);
ResultExpected = -1;
if (((g_VfsInodeEntries[Index].Mode & S_IWOTH) != 0) && ((g_VfsInodeEntries[Index].Mode & S_IXOTH) != 0))
{
ResultExpected = 0;
}
ResultActual = open(FileName, O_CREAT | O_RDONLY, S_IRUSR);
if (ResultActual != -1)
{
LxtClose(ResultActual);
ResultActual = 0;
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "directory create file", Index));
if (ResultActual == 0)
{
ResultActual = unlink(FileName);
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "directory delete file", Index));
}
//
// Check search (execute) access.
//
ErrnoExpected = EACCES;
ResultExpected = -1;
if ((g_VfsInodeEntries[Index].Mode & S_IXOTH) != 0)
{
ErrnoExpected = ENOENT;
}
ResultActual = open(FileName, O_RDONLY, 0);
if (ResultActual != -1)
{
LxtClose(ResultActual);
ResultActual = 0;
}
LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "directory search file", Index));
}
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
_exit(Result);
}
int VfsAccessInodeChecks(PLXT_ARGS Args)
/*++
--*/
{
int ChildPid;
int Result;
ChildPid = fork();
if (ChildPid == 0)
{
VfsAccessInodeChecksChild();
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
int VfsAccessParseArgs(int Argc, char* Argv[], LXT_ARGS* Args)
/*++
Routine Description:
This routine parses command line arguments for the vfsaccess tests.
Arguments:
Argc - Supplies the number of arguments.
Argv - Supplies an array of arguments.
Return Value:
Returns 0 on success, -1 on failure.
--*/
{
int ArgvIndex;
bool Cleanup;
const char* Name;
int Result;
int ValidArguments;
Result = LXT_RESULT_FAILURE;
ValidArguments = 0;
g_UseDrvFs = false;
Name = LXT_NAME;
Cleanup = true;
if (Argc < 1)
{
goto ErrorExit;
}
umask(0);
for (ArgvIndex = 1; ArgvIndex < Argc; ++ArgvIndex)
{
if (strcmp(Argv[ArgvIndex], "drvfs") == 0)
{
g_UseDrvFs = true;
Name = LXT_NAME_DRVFS;
continue;
}
if (Argv[ArgvIndex][0] != '-')
{
printf("Unexpected character %s", Argv[ArgvIndex]);
goto ErrorExit;
}
switch (Argv[ArgvIndex][1])
{
case 'c':
//
// Run the setusergroupid execve test child
//
ValidArguments = 1;
Cleanup = false;
Result = VfsAccessSetUserGroupIdExecveChild();
goto ErrorExit;
case 'v':
case 'l':
//
// This was already taken care of by LxtInitialize.
//
++ArgvIndex;
break;
case 'h':
case 'a':
break;
default:
goto ErrorExit;
}
}
//
// If -c was not specified, just run the tests
//
ValidArguments = 1;
LxtCheckResult(LxtInitialize(Argc, Argv, Args, Name));
LxtCheckResult(LxtFsTestSetup(Args, VFS_ACCESS_PARENT_DIR, "/vfsaccesstest", g_UseDrvFs));
if (Args->HelpRequested == false)
{
LxtLogInfo("Creating files.");
LxtCheckResult(VfsAccessFileObjectCreateFiles());
LxtCheckResult(VfsAccessFileObjectCreateSymlinks());
}
//
// Tests must be run forked since some of the tests change the uid and
// don't change it back, which breaks umount during cleanup.
//
LxtCheckResult(LxtRunVariationsForked(Args, g_LxtVariations, LXT_COUNT_OF(g_LxtVariations)));
ErrorExit:
if (ValidArguments == 0)
{
printf("\nuse: %s <One of the below arguments>\n", Argv[0]);
printf("\t-c : Run %s execve test child (don't use directly)\n", Argv[0]);
}
if (Cleanup != false)
{
VfsAccessFileObjectCleanup();
LxtFsTestCleanup(VFS_ACCESS_PARENT_DIR, "/vfsaccesstest", g_UseDrvFs);
}
return Result;
}
void VfsAccessUTimeCapChild(void)
/*++
--*/
{
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
int Fd;
int Result;
struct timeval Times[2];
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
CapData[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE);
CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN);
CapData[0].effective = CapData[0].permitted;
CapData[1].effective = CapData[1].permitted;
//
// Drop privileges so the current process does not have CAP_FOWNER.
//
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
LxtCheckErrno(setgid(VFS_ACCESS_UID));
LxtCheckErrno(setuid(VFS_ACCESS_UID));
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
//
// Create a file with a different user.
//
unlink(VFS_ACCESS_UTIME_FILE);
LxtCheckErrno(Fd = open(VFS_ACCESS_UTIME_FILE, O_CREAT, 0));
LxtClose(Fd);
LxtCheckErrno(chown(VFS_ACCESS_UTIME_FILE, VFS_ACCESS_UID + 1, VFS_ACCESS_UID + 1));
//
// Try to change the time on the file to 0.
//
memset(Times, 0, sizeof(Times));
LxtCheckErrnoFailure(utimes(VFS_ACCESS_UTIME_FILE, Times), EPERM);
Result = LXT_RESULT_SUCCESS;
ErrorExit:
unlink(VFS_ACCESS_UTIME_FILE);
_exit(Result);
}
int VfsAccessUTimeCap(PLXT_ARGS Args)
/*++
--*/
{
int ChildPid;
int Result;
ChildPid = fork();
if (ChildPid == 0)
{
VfsAccessUTimeCapChild();
}
LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
Result = LXT_RESULT_SUCCESS;
ErrorExit:
return Result;
}
int VfsAccessSetFsUid(PLXT_ARGS Args)
/*++
--*/
{
unsigned long long Effective;
struct __user_cap_data_struct CapData[2];
struct __user_cap_header_struct CapHeader;
struct __user_cap_data_struct ExpectedCapData[2];
int Fd;
int Index;
struct passwd* Password;
int Result;
memset(&CapData, 0, sizeof(CapData));
memset(&CapHeader, 0, sizeof(CapHeader));
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
Fd = -1;
Result = LXT_RESULT_FAILURE;
//
// Get the password entry for the 'nobody' user.
//
Password = getpwnam("nobody");
if (Password == NULL)
{
goto ErrorExit;
}
//
// Create a file to be used for access checks.
//
Fd = open(VFS_ACCESS_FSUID_FILE, O_CREAT | O_RDWR, 0644);
if (Fd < 0)
{
goto ErrorExit;
}
//
// Get the original capabilities.
//
LxtCheckErrno(LxtCapGet(&CapHeader, ExpectedCapData));
Effective = (((unsigned long long)ExpectedCapData[0].effective) << 32) | ExpectedCapData[1].effective;
LxtLogInfo("Before setfsuid(nobody) %016llX", Effective);
//
// Set the fsuid and ensure that the correct capabilities are dropped when
// switching from root.
//
if (LxtSetfsuid(Password->pw_uid) < 0)
{
goto ErrorExit;
}
LxtCheckErrno(LxtCapGet(&CapHeader, CapData));
Effective = (((unsigned long long)CapData[0].effective) << 32) | CapData[1].effective;
LxtLogInfo("After setfsuid(nobody) %016llX", Effective);
for (Index = 0; Index < LXT_COUNT_OF(g_VfsSetFsUidCaps); Index++)
{
ExpectedCapData[CAP_TO_INDEX(g_VfsSetFsUidCaps[Index])].effective &= ~CAP_TO_MASK(g_VfsSetFsUidCaps[Index]);
}
if ((CapData[0].effective != ExpectedCapData[0].effective) || (CapData[1].effective != ExpectedCapData[1].effective))
{
LxtLogError("Capabilities do not match expected");
Result = LXT_RESULT_FAILURE;
goto ErrorExit;
}
//
// Verify that opening the file fails since we no longer have the correct fsuid or capabilities.
//
LxtCheckErrnoFailure(open(VFS_ACCESS_FSUID_FILE, O_RDWR), EACCES);
//
// Set the fsuid back to root and verify that the capabilities were correctly restored.
//
LxtCheckErrno(LxtSetfsuid(0));
LxtCheckErrno(LxtCapGet(&CapHeader, CapData));
Effective = (((unsigned long long)CapData[0].effective) << 32) | CapData[1].effective;
LxtLogInfo("After setfsuid(root) %016llX", Effective);
for (Index = 0; Index < LXT_COUNT_OF(g_VfsSetFsUidCaps); Index++)
{
ExpectedCapData[CAP_TO_INDEX(g_VfsSetFsUidCaps[Index])].effective |= CAP_TO_MASK(g_VfsSetFsUidCaps[Index]);
}
if ((CapData[0].effective != ExpectedCapData[0].effective) || (CapData[1].effective != ExpectedCapData[1].effective))
{
LxtLogError("Capabilities do not match expected");
Result = LXT_RESULT_FAILURE;
goto ErrorExit;
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (Fd != -1)
{
LxtClose(Fd);
}
unlink(VFS_ACCESS_FSUID_FILE);
return Result;
}
void getreuid(struct reuid_t* Set)
{
getresuid(&Set->r, &Set->e, &Set->s);
// LxtLogInfo("getresuid(%d,%d,%d)",Set->r,Set->e,Set->s);
}
pid_t fork_wait()
{
pid_t pid;
int status = 0;
if ((pid = fork()) == 0)
{
return pid;
}
else
{
waitpid(pid, &status, 0);
}
return pid;
}
int VfsAccessSetUid(PLXT_ARGS Args)
{
struct reuid_t Original;
struct reuid_t Set;
struct passwd* Nobody;
int Result;
getreuid(&Original);
LxtLogInfo("Current UID: %d", Original.r);
LxtLogInfo("Current EUID: %d", Original.e);
LxtLogInfo("Current SUID: %d", Original.s);
//
// Try setting without changing
//
setreuid(-1, -1);
getreuid(&Set);
LxtCheckEqual(Set.r, Original.r, "%d");
LxtCheckEqual(Set.e, Original.e, "%d");
LxtCheckEqual(Set.s, Original.s, "%d");
//
// More tests possible when run as root.
//
if (Original.r == 0 || Original.e == 0)
{
Nobody = getpwnam("nobody");
if (Nobody == NULL)
{
Result = LXT_RESULT_FAILURE;
LxtLogError("Couldn't get details for user 'nobody'");
goto ErrorExit;
}
LxtLogInfo("Attempting setreuid(%d, -1)", Nobody->pw_uid);
LxtCheckResult(setreuid(Nobody->pw_uid, -1));
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != 0 || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
//
// reset state to 0, 0, 0
//
LxtLogInfo("setuid(0)");
LxtCheckResult(setuid(0));
getreuid(&Set);
if (Set.r != 0 || Set.e != 0 || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
return -1; // Fatal, Nobody tests rely on this succeeding
}
//
// This test checks that setuid only touches the ruid and suid values.
//
LxtLogInfo("setresuid(-1, %d, %d)", Nobody->pw_uid, Nobody->pw_uid);
LxtCheckResult(setresuid(-1, Nobody->pw_uid, Nobody->pw_uid));
getreuid(&Set);
if (Set.r != 0 || Set.e != Nobody->pw_uid || Set.s != Nobody->pw_uid)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
//
// Set state to 0, 0, 65534
//
LxtLogInfo("Attempting setuid(0)");
LxtCheckResult(setuid(0));
getreuid(&Set);
if (Set.r != 0 || Set.e != 0 || Set.s != 65534)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
//
// This test checks the first transitive property of setreuid wherein
// setting the effective uid also sets the suid
//
// Reset state to 0, 0, 0
//
LxtLogInfo("setresuid(0, 0, 0)");
LxtCheckResult(setresuid(0, 0, 0));
getreuid(&Set);
if (Set.r != 0 || Set.e != 0 || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
return -1; // Fatal, Other tests rely on this succeeding
}
LxtLogInfo("setreuid(-1, %d)", Nobody->pw_uid);
LxtCheckResult(setreuid(-1, Nobody->pw_uid));
getreuid(&Set);
if (Set.r != 0 || Set.e != Nobody->pw_uid || Set.s != Nobody->pw_uid)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
//
// Set state to 0, 0, 65534
//
LxtLogInfo("Attempting setuid(0)");
LxtCheckResult(setuid(0));
getreuid(&Set);
if (Set.r != 0 || Set.e != 0 || Set.s != 65534)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
//
// This test checks the second transitive property of setreuid
// wherein setting the ruid, but not the euid will Set the suid
// to be the euid
//
LxtLogInfo("setresuid(%d, 0, VFS_ACCESS_UID)", Nobody->pw_uid);
LxtCheckResult(setresuid(Nobody->pw_uid, 0, VFS_ACCESS_UID));
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != 0 || Set.s != VFS_ACCESS_UID)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
LxtLogInfo("Attempting setreuid(0, -1)");
LxtCheckResult(setreuid(0, -1));
getreuid(&Set);
if (Set.r != 0 || Set.e != 0 || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
//
// This test checks that unprivileged processes can Set the euid
// to the ruid or suid. Need to fork and wait as privileges are
// irreversibly dropped by this syscall
//
if (fork_wait() == 0)
{
LxtLogInfo("setresuid(%d, VFS_ACCESS_UID, 0)", Nobody->pw_uid);
LxtCheckResult(setresuid(Nobody->pw_uid, VFS_ACCESS_UID, 0));
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != VFS_ACCESS_UID || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
LxtLogInfo("Attempting setreuid(-1, %d)", Nobody->pw_uid);
LxtCheckResult(setreuid(-1, Nobody->pw_uid));
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != Nobody->pw_uid || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
exit(0);
}
//
// This test checks that unprivileged processes can Set the euid
// to the ruid or suid.
//
LxtLogInfo("setresuid(%d, %d, 0)", Nobody->pw_uid, Nobody->pw_uid);
LxtCheckResult(setresuid(Nobody->pw_uid, Nobody->pw_uid, 0));
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != Nobody->pw_uid || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
LxtLogInfo("Attempting setreuid(-1, 0)");
LxtCheckResult(setreuid(-1, 0));
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != 0 || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
//
// Reset state to 0, 0, 0
//
LxtLogInfo("setresuid(0, 0, 0)");
LxtCheckResult(setresuid(0, 0, 0));
getreuid(&Set);
if (Set.r != 0 || Set.e != 0 || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
return -1; // Fatal, Other tests rely on this succeeding
}
//
// This test validates that unprivileged users can only Set the ruid
// to the ruid or the euid
//
if (fork_wait() == 0)
{
LxtLogInfo("setresuid(%d, VFS_ACCESS_UID, 0)", Nobody->pw_uid);
LxtCheckResult(setresuid(Nobody->pw_uid, VFS_ACCESS_UID, 0));
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != VFS_ACCESS_UID || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
LxtLogInfo("Attempting setreuid(VFS_ACCESS_UID, -1)");
LxtCheckResult(setreuid(VFS_ACCESS_UID, -1));
getreuid(&Set);
if (Set.r != VFS_ACCESS_UID || Set.e != VFS_ACCESS_UID || Set.s != VFS_ACCESS_UID)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
exit(0);
}
if (fork_wait() == 0)
{
LxtLogInfo("setresuid(%d, VFS_ACCESS_UID, 0)", Nobody->pw_uid);
LxtCheckResult(setresuid(Nobody->pw_uid, VFS_ACCESS_UID, 0));
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != VFS_ACCESS_UID || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
LxtLogInfo("Attempting setreuid(0, -1)");
LxtCheckErrnoFailure(setreuid(0, -1), EPERM);
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != VFS_ACCESS_UID || Set.s != 0)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
}
exit(0);
}
//
// Drop all permissions permanently
//
LxtLogInfo("Dropping all permissions");
LxtLogInfo("setresuid(%d, %d, %d)", Nobody->pw_uid, Nobody->pw_uid, Nobody->pw_uid);
LxtCheckResult(setresuid(Nobody->pw_uid, Nobody->pw_uid, Nobody->pw_uid));
getreuid(&Set);
if (Set.r != Nobody->pw_uid || Set.e != Nobody->pw_uid)
{
LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s);
return -1;
}
}
//
// Try to gain root uid
//
LxtLogInfo("Attempting setreuid(0, -1)");
LxtCheckErrnoFailure(setreuid(0, -1), EPERM);
getreuid(&Set);
if (Set.r == 0 || Set.e == 0)
{
LxtLogError("Gained root permissions!");
}
LxtLogInfo("Attempting setreuid(-1, 0)");
LxtCheckErrnoFailure(setreuid(-1, 0), EPERM);
getreuid(&Set);
if (Set.r == 0 || Set.e == 0)
{
LxtLogError("Gained root permissions!");
}
LxtLogInfo("Attempting setreuid(0, 0)");
LxtCheckErrnoFailure(setreuid(0, 0), EPERM);
getreuid(&Set);
if (Set.r == 0 || Set.e == 0)
{
LxtLogError("Gained root permissions!");
}
ErrorExit:
return Result;
}