Blob Blame History Raw
/***********************************************************/
/*  ntux: native translation und extension                 */
/*  Copyright (C) 2016--2021  SysDeer Technologies, LLC    */
/*  Released under GPLv2 and GPLv3; see COPYING.NTUX.      */
/***********************************************************/

#include <psxabi/sys_sysapi.h>
#include <psxabi/sys_stat.h>
#include <psxabi/sys_path.h>
#include <psxabi/sys_errno.h>

#include <psxxfi/xfi_base.h>
#include <psxxfi/xfi_acl.h>
#include <psxxfi/xfi_fs.h>
#include <psxxfi/xfi_ofd.h>
#include <psxxfi/xfi_path.h>
#include <psxxfi/xfi_unicode.h>

#include <ntapi/nt_object.h>
#include <ntapi/nt_acl.h>
#include <ntapi/nt_file.h>

#include <ntux/ntux.h>
#include "ntux_driver_impl.h"
#include "ntux_nolibc_impl.h"
#include "ntux_errinfo_impl.h"
#include "nolibc/stdbool.h"

#define __SID_SYSTEM			{1,1,{{0,0,0,0,0,5}},{18}}
#define __SID_ADMINISTRATORS		{1,2,{{0,0,0,0,0,5}},{32,544}}

#define NTUX_OPT_MODE_NEUTRAL		(0)
#define NTUX_OPT_MODE_EQUAL		(1)
#define NTUX_OPT_MODE_PLUS		(2)
#define NTUX_OPT_MODE_MINUS		(3)

#define NTUX_BUF_SIZE_64K		(64*1024)

static const nt_sid    sid_system       = __SID_SYSTEM;
static const nt_sid_os sid_admins       = __SID_ADMINISTRATORS;

struct ntux_sd_buffer {
	nt_sd_common_buffer	sd;
	uint32_t		exbuf[512];
};

struct ntux_ace_any {
        nt_ace_header   header;
        uint32_t        mask;
        uint32_t        sid_start;
};

static int ntux_cmd_chmod_ret(int fd, struct __ofd * ofd, void * hasync, int ret)
{
	if (hasync)
		__xfi_close_handle(hasync);

	if (ofd)
		__xfi_ofd_ref_dec(ofd);

	if (fd >= 0)
		__sys_close(fd);

	return ret;
}

static nt_sid * ntux_cmd_chmod_sid_from_name(const char * name)
{
	if (!strcmp(name,"Administrators"))
		return (nt_sid *)&sid_admins;

	else if (!strcmp(name,"SYSTEM"))
		return (nt_sid *)&sid_system;

	else
		return 0;
}

int ntux_cmd_chmod(const struct ntux_driver_ctx * dctx, const char * dunit)
{
	intptr_t		ret;
	int32_t			status;
	int			fdout;
	int			fdcwd;
	int			finherit;
	int			optmode;
	const char *		strmode;
	const char *		chmode;
	const unsigned char *	refobj;
	const unsigned char *	unit;
	char *			ch;
	struct ntux_sd_buffer	sdbuf;
	struct ntux_sd_buffer	srcsd;
	struct ntux_sd_buffer	cntsd;
	nt_sd_common_buffer *	dstsd;
	nt_sd_common_meta	meta;
	nt_sid *		owner;
	nt_sid *		group;
	nt_acl *		dacl;
	struct ntux_ace_any *	srcace;
	struct ntux_ace_any *	dstace;
	uint32_t		access_owner;
	uint32_t		access_group;
	uint32_t		access_other;
	uint32_t		access_admin;
	uint32_t		ace_flags;
	uint32_t		sec_mask;
	size_t			size;
	size_t			addr;
	size_t			aceaddr;
	size_t			cntbufsize;
	char *			cntpath_utf8;
	wchar16_t *		cntpath_utf16;
	int			exacefilter;
	size_t			exacesize;
	int			exacecount;
	int			idx;
	int			fd      = -1;
	int			reffd   = -1;
	int			cntfd   = -1;
	struct __ofd *		ofd     = 0;
	struct __ofd *		refofd  = 0;
	struct __ofd *		cntofd  = 0;
	void *			cntbuf  = 0;
	void *			hasync  = 0;
	char			dummy   = 0;

	/* strmode */
	strmode   = dctx->cctx->strmode ? dctx->cctx->strmode : &dummy;
	optmode   = NTUX_OPT_MODE_NEUTRAL;
	finherit  = false;
	ace_flags = 0;

	for (chmode=strmode; *chmode; chmode++) {
		switch (*chmode) {
			case '=':
				optmode   = NTUX_OPT_MODE_EQUAL;
				finherit  = 0;
				ace_flags = 0;
				break;

			case '+':
				optmode = NTUX_OPT_MODE_PLUS;
				break;

			case '-':
				optmode = NTUX_OPT_MODE_MINUS;
				break;

			case 'p':
				switch (optmode) {
					case NTUX_OPT_MODE_EQUAL:
					case NTUX_OPT_MODE_PLUS:
						ace_flags = NT_ACE_CONTAINER_INHERIT | NT_ACE_OBJECT_INHERIT;
						break;

					case NTUX_OPT_MODE_MINUS:
						ace_flags = 0;
						break;

					default:
						return ntux_cmd_chmod_ret(
							0,0,0,
							NTUX_CUSTOM_ERROR(
								dctx,
								NTUX_ERR_FLEE_ERROR));
				}

				break;

			case 'P':
				switch (optmode) {
					case NTUX_OPT_MODE_EQUAL:
					case NTUX_OPT_MODE_PLUS:
						finherit = true;
						break;

					case NTUX_OPT_MODE_MINUS:
						finherit = false;
						break;

					default:
						return ntux_cmd_chmod_ret(
							0,0,0,
							NTUX_CUSTOM_ERROR(
								dctx,
								NTUX_ERR_FLEE_ERROR));
				}

				break;

			default:
				return ntux_cmd_chmod_ret(
					0,0,0,
					NTUX_CUSTOM_ERROR(
						dctx,
						NTUX_ERR_FLEE_ERROR));
		}
	}

	/* conflicting arguments? */
	if (finherit && dctx->cctx->refobj)
		return ntux_cmd_chmod_ret(
			0,0,0,
			NTUX_CUSTOM_ERROR(
				dctx,
				NTUX_ERR_CONFLICTING_ARGUMENTS));


	/* initial --owner and --group support: Administrators, SYSTEM */
	owner = 0;
	group = 0;

	if (dctx->cctx->owner)
		if (!(owner = ntux_cmd_chmod_sid_from_name(dctx->cctx->owner)))
			return ntux_cmd_chmod_ret(
				0,0,0,
				NTUX_CUSTOM_ERROR(
					dctx,
					NTUX_ERR_NOT_IMPLEMENTED));

	if (dctx->cctx->group)
		if (!(group = ntux_cmd_chmod_sid_from_name(dctx->cctx->group)))
			return ntux_cmd_chmod_ret(
				0,0,0,
				NTUX_CUSTOM_ERROR(
					dctx,
					NTUX_ERR_NOT_IMPLEMENTED));

	/* init */
	ntux_driver_set_ectx(
		dctx,0,dunit);

	unit = (const unsigned char *)dunit;

	/* fdctx */
	fdout = ntux_driver_fdout(dctx);
	fdcwd = ntux_driver_fdcwd(dctx);

	/* fd */
	if ((ret = __sys_openat(fdcwd,unit,0,0)) < 0)
		if (ntux_errno_set(dctx,ret))
			return ntux_cmd_chmod_ret(
				0,0,0,
				NTUX_SYSTEM_ERROR(dctx));

	fd = ret;

	/* ofd */
	if (!(ofd = __xfi_ofd_ref_inc(fd)))
		return ntux_cmd_chmod_ret(
			fd,0,0,
			NTUX_CUSTOM_ERROR(
				dctx,
				NTUX_ERR_FLOW_ERROR));


	/* hasync */
	sec_mask  = NT_SEC_READ_CONTROL;
	sec_mask |= NT_SEC_WRITE_DAC;
	sec_mask |= owner ? NT_SEC_WRITE_OWNER : 0;

	if ((status = __xfi_fs_open_async(
			&hasync,
			ofd->info.hfile,0,
			sec_mask,
			NT_FILE_SHARE_READ
				| NT_FILE_SHARE_WRITE
				| NT_FILE_SHARE_DELETE)))
		if (ntux_errno_set(dctx,EACCES))
			return ntux_cmd_chmod_ret(
				fd,ofd,0,
				NTUX_SYSTEM_ERROR(dctx));

	/* refofd */
	if ((refobj = (const unsigned char *)dctx->cctx->refobj)) {
		ntux_driver_set_ectx(
			dctx,0,dctx->cctx->refobj);

		if ((ret = __sys_openat(fdcwd,refobj,0,0)) < 0)
			if (ntux_errno_set(dctx,ret))
				return ntux_cmd_chmod_ret(
					0,0,0,
					NTUX_SYSTEM_ERROR(dctx));

		reffd = ret;

		if (!(refofd = __xfi_ofd_ref_inc(reffd))) {
			__sys_close(reffd);

			return ntux_cmd_chmod_ret(
				fd,0,0,
				NTUX_CUSTOM_ERROR(
					dctx,
					NTUX_ERR_FLOW_ERROR));
		}

		ntux_driver_set_ectx(
			dctx,0,dunit);
	}

	/* srcsd */
	status = __xfi_query_security_object(
		refobj ? refofd->info.hfile : hasync,
		NT_OWNER_SECURITY_INFORMATION
			| NT_GROUP_SECURITY_INFORMATION
			| NT_DACL_SECURITY_INFORMATION,
		&srcsd.sd.sd,sizeof(srcsd),&size);

	if (refofd) {
		__xfi_ofd_ref_dec(refofd);
		__sys_close(reffd);
	}

	if (status)
		if (ntux_errno_set(dctx,ENXIO))
			return ntux_cmd_chmod_ret(
				fd,ofd,hasync,
				NTUX_SYSTEM_ERROR(dctx));

	status = __xfi_acl_init_common_descriptor_meta(
		&meta,&srcsd.sd.sd,
		NT_ACL_INIT_COMMON_DESCRIPTION_META_STRICT_MODE);

	/* sd copy based on a non-posix object? */
	if (status && refobj) {
		ntux_driver_set_ectx(
			dctx,0,dctx->cctx->refobj);

		if (dctx->cctx->strmode
				|| dctx->cctx->owner
				|| dctx->cctx->group)
			return ntux_cmd_chmod_ret(
				0,0,0,
				NTUX_CUSTOM_ERROR(
					dctx,
					NTUX_ERR_NON_POSIX_DESCRIPTOR));

		ntux_driver_set_ectx(
			dctx,0,dunit);

		sec_mask = NT_DACL_SECURITY_INFORMATION;

		if ((status = __xfi_set_security_object(
				hasync,sec_mask,&srcsd.sd.sd)))
			if (ntux_errno_set(dctx,EPERM))
				return ntux_cmd_chmod_ret(
					fd,ofd,hasync,
					NTUX_SYSTEM_ERROR(dctx));

		return ntux_cmd_chmod_ret(fd,ofd,hasync,0);
	}

	if (status)
		if (ntux_errno_set(dctx,EBADF))
			return ntux_cmd_chmod_ret(
				fd,ofd,hasync,
				NTUX_SYSTEM_ERROR(dctx));

	/* source permissions */
	access_owner  = meta.owner_ace  ? meta.owner_ace->mask  : 0;
	access_group  = meta.group_ace  ? meta.group_ace->mask  : 0;
	access_other  = meta.other_ace  ? meta.other_ace->mask  : 0;
	access_admin  = meta.admin_ace  ? meta.admin_ace->mask  : 0;

	/* initial --strmode support, retaining previous options as needed */
	if (!dctx->cctx->strmode) {
		ace_flags |= meta.owner_ace  ? meta.owner_ace->header.ace_flags : 0;
		ace_flags |= meta.group_ace  ? meta.group_ace->header.ace_flags : 0;
		ace_flags |= meta.other_ace  ? meta.other_ace->header.ace_flags : 0;
		ace_flags |= meta.admin_ace  ? meta.admin_ace->header.ace_flags : 0;
	}

	/* updated dacl */
	dstsd = &sdbuf.sd;

	__xfi_acl_init_common_descriptor(
		dstsd,
		owner ? owner : meta.owner,
		group ? group : meta.group,
		0,0,
		access_owner,access_group,access_other,
		access_admin,meta.system_acc,
		ace_flags);

	if ((dstsd->sd.offset_dacl < dstsd->sd.offset_owner)
			|| (dstsd->sd.offset_dacl < dstsd->sd.offset_group)
			|| (dstsd->sd.offset_dacl < dstsd->sd.offset_sacl))
		return ntux_cmd_chmod_ret(
			fd,0,0,
			NTUX_CUSTOM_ERROR(
				dctx,
				NTUX_ERR_FLOW_ERROR));

	/* inherited ace's */
	if (finherit) {
		dstsd->sd.control = NT_SE_SELF_RELATIVE
			| NT_SE_DACL_AUTO_INHERIT_REQ
			| NT_SE_DACL_AUTO_INHERITED
			| NT_SE_DACL_PRESENT;

		/* cntofd */
		cntbuf = 0; cntbufsize = NTUX_BUF_SIZE_64K;

		if (__xfi_allocate_virtual_memory(
				NT_CURRENT_PROCESS_HANDLE,
				&cntbuf,
				0,
				&cntbufsize,
				NT_MEM_COMMIT,
				NT_PAGE_READWRITE))
			return ntux_cmd_chmod_ret(
				fd,ofd,hasync,
				NTUX_SYSTEM_ERROR(dctx));

		if (__xfi_ofd_is_dir(ofd)) {
			cntpath_utf16 = (wchar16_t *)cntbuf;

			__xfi_path_conv_ofdat_utf16(
				ofd,
				(const wchar16_t[]){'.','.','/',0},
				PSX_PATH_SYNTAX_ANY,
				4*sizeof(wchar16_t),
				cntpath_utf16,
				PSX_PATH_SYNTAX_POSIX_STRICT,
				cntbufsize,
				0,0,PSX_PATH_CONV_RESOLVE_ELEMENT,
				&cntofd);
		} else {
			cntpath_utf8 = cntbuf;

			status = __xfi_path_conv_utf8(
				fdcwd,
				unit,
				PSX_PATH_SYNTAX_POSIX_STRICT,
				ntux_strlen((const char *)unit),
				cntpath_utf8,
				PSX_PATH_SYNTAX_POSIX_STRICT,
				cntbufsize,
				0,0,PSX_PATH_CONV_RESOLVE_ELEMENT,
				0);

			if (status) {
				__xfi_free_virtual_memory(
					NT_CURRENT_PROCESS_HANDLE,
					&cntbuf,
					&cntbufsize,
					NT_MEM_RELEASE);

				return ntux_cmd_chmod_ret(
					fd,ofd,hasync,
					NTUX_CUSTOM_ERROR(
						dctx,
						NTUX_ERR_FLOW_ERROR));
			}

			if ((cntpath_utf8[0] == '/') && cntpath_utf8[1]) {
				for (ch=cntpath_utf8; *ch; ch++)
					(void)0;

				/* guard against an internal error */
				if (ch[-1] != '/') {
					for (--ch; *ch != '/'; ch--)
						(void)0;

					*ch = 0;

					cntfd = __sys_openat(
						fdcwd,
						(const unsigned char *)cntpath_utf8,
						0,0);

					if (cntfd >= 0)
						if (!(cntofd = __xfi_ofd_ref_inc(cntfd)))
							__sys_close(cntfd);
				}
			}
		}

		__xfi_free_virtual_memory(
			NT_CURRENT_PROCESS_HANDLE,
			&cntbuf,
			&cntbufsize,
			NT_MEM_RELEASE);

		if (!cntofd)
			return ntux_cmd_chmod_ret(
				fd,ofd,hasync,
				NTUX_CUSTOM_ERROR(
					dctx,
					NTUX_ERR_FLOW_ERROR));

		status = __xfi_query_security_object(
			cntofd->info.hfile,
			NT_OWNER_SECURITY_INFORMATION
				| NT_GROUP_SECURITY_INFORMATION
				| NT_DACL_SECURITY_INFORMATION,
			&cntsd.sd.sd,sizeof(cntsd),&size);

		if (status) {
			__xfi_ofd_ref_dec(cntofd);
			__sys_close(cntfd);

			return ntux_cmd_chmod_ret(
				fd,ofd,hasync,
				NTUX_SYSTEM_ERROR(dctx));
		}

		/* exacefilter */
		exacefilter = __xfi_ofd_is_dir(ofd)
			? NT_ACE_CONTAINER_INHERIT
			: NT_ACE_OBJECT_INHERIT;

		/* exacecount, exacesize */
		if (cntsd.sd.sd.offset_dacl) {
			addr  = (size_t)&cntsd.sd.sd;
			addr += cntsd.sd.sd.offset_dacl;
			dacl  = (nt_acl *)addr;

			addr += sizeof(*dacl);

			exacecount = 0;
			exacesize  = 0;

			for (idx=0; idx<dacl->ace_count; idx++) {
				srcace = (struct ntux_ace_any *)addr;

				if (srcace->header.ace_flags & exacefilter) {
					exacecount++;
					exacesize += srcace->header.ace_size;
				}

				addr += srcace->header.ace_size;
			}


			/* dstsd::dacl */
			addr  = (size_t)dstsd;
			addr += dstsd->sd.offset_dacl;
			dacl  = (nt_acl *)addr;

			if (dstsd->sd.offset_dacl + dacl->acl_size + exacesize > sizeof(sdbuf)) {
				__xfi_ofd_ref_dec(cntofd);
				__sys_close(cntfd);

				return ntux_cmd_chmod_ret(
					fd,ofd,hasync,
					NTUX_BUFFER_ERROR(dctx));
			}

			dacl->acl_size  += exacesize;
			dacl->ace_count += exacecount;

			/* pointer to dstsd's next ace */
			aceaddr  = addr;
			aceaddr += sizeof(*dacl);

			for (idx=0; idx<dacl->ace_count; idx++) {
				dstace   = (struct ntux_ace_any *)aceaddr;
				aceaddr += dstace->header.ace_size;
			}

			dstace = (struct ntux_ace_any *)aceaddr;

			/* copy container's ace's according to filter */
			addr  = (size_t)&cntsd.sd.sd;
			addr += cntsd.sd.sd.offset_dacl;
			dacl  = (nt_acl *)addr;
			addr += sizeof(*dacl);

			for (idx=0; idx<dacl->ace_count; idx++) {
				srcace = (struct ntux_ace_any *)addr;

				if (srcace->header.ace_flags & exacefilter) {
					ntux_memcpy(dstace,srcace,srcace->header.ace_size);
					dstace->header.ace_flags = NT_ACE_INHERITED | ace_flags;

					aceaddr += srcace->header.ace_size;
					dstace   = (struct ntux_ace_any *)aceaddr;
				}

				addr += srcace->header.ace_size;
			}
		}
	}

	/* finalize */
	sec_mask  = NT_DACL_SECURITY_INFORMATION;
	sec_mask |= owner ? NT_OWNER_SECURITY_INFORMATION : 0;

	if ((status = __xfi_set_security_object(
			hasync,sec_mask,&dstsd->sd)))
		if (ntux_errno_set(dctx,EPERM))
			return ntux_cmd_chmod_ret(
				fd,ofd,hasync,
				NTUX_SYSTEM_ERROR(dctx));

	/* changes */
	(void)fdout;

	/* cntofd */
	if (cntofd) {
		__xfi_ofd_ref_dec(cntofd);
		__sys_close(cntfd);
	}

	/* all done */
	return ntux_cmd_chmod_ret(fd,ofd,hasync,0);
}