From 6496a2c1bca197f6ad7e05624a5cac9744da1c28 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 23 Aug 2020 23:35:00 -0700 Subject: [PATCH] Honor permissions in OpenFileSystemWithId. Add basic AC test --- build/CodeGen/result_modules.csv | 1 + build/CodeGen/result_paths.csv | 1 + build/CodeGen/results.csv | 49 ++++++++ src/LibHac/Fs/FileSystemClient.cs | 6 +- src/LibHac/FsSrv/FileSystemProxy.cs | 49 +++++++- .../FsSrv/Impl/ProgramRegistryManager.cs | 2 +- src/LibHac/FsSrv/Sf/FspPath.cs | 2 +- src/LibHac/FsSystem/FsPath.cs | 2 +- src/LibHac/InternalsVisibleToTests.cs | 3 + src/LibHac/Sm/ServiceManager.cs | 4 +- src/LibHac/Svc/ResultSvc.cs | 107 ++++++++++++++++++ .../LibHac.Tests/FsSrv/AccessControlTests.cs | 64 +++++++++++ tests/LibHac.Tests/HorizonFactory.cs | 25 ++++ 13 files changed, 304 insertions(+), 11 deletions(-) create mode 100644 src/LibHac/InternalsVisibleToTests.cs create mode 100644 src/LibHac/Svc/ResultSvc.cs create mode 100644 tests/LibHac.Tests/FsSrv/AccessControlTests.cs create mode 100644 tests/LibHac.Tests/HorizonFactory.cs diff --git a/build/CodeGen/result_modules.csv b/build/CodeGen/result_modules.csv index ac03ac76..9cda319c 100644 --- a/build/CodeGen/result_modules.csv +++ b/build/CodeGen/result_modules.csv @@ -1,4 +1,5 @@ Name,Index +Svc,1 Fs,2 Loader,9 Sf,10 diff --git a/build/CodeGen/result_paths.csv b/build/CodeGen/result_paths.csv index d1f5e538..80c6be94 100644 --- a/build/CodeGen/result_paths.csv +++ b/build/CodeGen/result_paths.csv @@ -1,4 +1,5 @@ Name,Namespace,Path +Svc,LibHac.Svc,LibHac/Svc/ResultSvc.cs Fs,LibHac.Fs,LibHac/Fs/ResultFs.cs Loader,LibHac.Loader,LibHac/Loader/ResultLoader.cs Sf,LibHac.Sf,LibHac/Sf/ResultSf.cs diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index 49bd974d..95a37997 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -1,4 +1,53 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary + +1,7,,OutOfSessions, +1,14,,InvalidArgument, +1,33,,NotImplemented, +1,54,,StopProcessingException, +1,57,,NoSynchronizationObject, +1,59,,TerminationRequested, +1,70,,NoEvent, + +1,101,,InvalidSize, +1,102,,InvalidAddress, +1,103,,OutOfResource, +1,104,,OutOfMemory, +1,105,,OutOfHandles, +1,106,,InvalidCurrentMemory, +1,108,,InvalidNewMemoryPermission, +1,110,,InvalidMemoryRegion, +1,112,,InvalidPriority, +1,113,,InvalidCoreId, +1,114,,InvalidHandle, +1,115,,InvalidPointer, +1,116,,InvalidCombination, +1,117,,TimedOut, +1,118,,Cancelled, +1,119,,OutOfRange, +1,120,,InvalidEnumValue, +1,121,,NotFound, +1,122,,Busy, +1,123,,SessionClosed, +1,124,,NotHandled, +1,125,,InvalidState, +1,126,,ReservedUsed, +1,127,,NotSupported, +1,128,,Debug, +1,129,,NoThread, +1,130,,UnknownThread, +1,131,,PortClosed, +1,132,,LimitReached, +1,133,,InvalidMemoryPool, + +1,258,,ReceiveListBroken, +1,259,,OutOfAddressSpace, +1,260,,MessageTooLarge, + +1,517,,InvalidProcessId, +1,518,,InvalidThreadId, +1,519,,InvalidId, +1,520,,ProcessTerminated, + 2,0,999,HandledByAllProcess, 2,1,,PathNotFound,Specified path does not exist 2,2,,PathAlreadyExists,Specified path already exists diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index 9f4f124f..0d42e86f 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -64,7 +64,7 @@ namespace LibHac.Fs if (rc.IsFailure()) { - throw new HorizonResultException(rc, "Failed to create file system proxy service object."); + throw new HorizonResultException(rc, "Failed to get file system proxy service object."); } fsProxy.SetCurrentProcess(Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); @@ -91,7 +91,7 @@ namespace LibHac.Fs if (rc.IsFailure()) { - throw new HorizonResultException(rc, "Failed to create file system proxy service object."); + throw new HorizonResultException(rc, "Failed to get file system proxy service object."); } fsProxy.SetCurrentProcess(Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); @@ -118,7 +118,7 @@ namespace LibHac.Fs if (rc.IsFailure()) { - throw new HorizonResultException(rc, "Failed to create registry service object."); + throw new HorizonResultException(rc, "Failed to get registry service object."); } ProgramRegistry = registry; diff --git a/src/LibHac/FsSrv/FileSystemProxy.cs b/src/LibHac/FsSrv/FileSystemProxy.cs index 3334b3e7..e31d7fe6 100644 --- a/src/LibHac/FsSrv/FileSystemProxy.cs +++ b/src/LibHac/FsSrv/FileSystemProxy.cs @@ -45,15 +45,58 @@ namespace LibHac.FsSrv { fileSystem = default; - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - bool canMountSystemDataPrivate = false; + AccessControl ac = programInfo.AccessControl; + + switch (type) + { + case FileSystemProxyType.Logo: + if (!ac.GetAccessibilityFor(AccessibilityType.MountLogo).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Control: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentControl).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Manual: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentManual).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Meta: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentMeta).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Data: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentData).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Package: + if (!ac.GetAccessibilityFor(AccessibilityType.MountApplicationPackage).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + default: + return ResultFs.InvalidArgument.Log(); + } + + if (type == FileSystemProxyType.Meta) + { + id = ulong.MaxValue; + } + else if (id == ulong.MaxValue) + { + return ResultFs.InvalidArgument.Log(); + } + + bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead; var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path)); if (normalizer.Result.IsFailure()) return normalizer.Result; - // ReSharper disable once ConditionIsAlwaysTrueOrFalse return FsProxyCore.OpenFileSystem(out fileSystem, normalizer.Path, type, canMountSystemDataPrivate, id); + + // Missing speed emulation storage type wrapper, async wrapper, and FileSystemInterfaceAdapter } private PathNormalizer.Option GetPathNormalizerOptions(U8Span path) diff --git a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs index dc23ca15..8105b824 100644 --- a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs +++ b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs @@ -180,7 +180,7 @@ namespace LibHac.FsSrv.Impl const int initialProcessIdLowerBound = 1; const int initialProcessIdUpperBound = 0x50; - return initialProcessIdLowerBound >= processId && processId <= initialProcessIdUpperBound; + return initialProcessIdLowerBound <= processId && processId <= initialProcessIdUpperBound; } internal static ProgramInfo CreateProgramInfoForInitialProcess(FileSystemServer fsServer) diff --git a/src/LibHac/FsSrv/Sf/FspPath.cs b/src/LibHac/FsSrv/Sf/FspPath.cs index 8b3359be..49b3e20b 100644 --- a/src/LibHac/FsSrv/Sf/FspPath.cs +++ b/src/LibHac/FsSrv/Sf/FspPath.cs @@ -29,7 +29,7 @@ namespace LibHac.FsSrv.Sf Span str = SpanHelpers.AsByteSpan(ref fspPath); // Ensure null terminator even if the creation fails for safety - str[0x301] = 0; + str[MaxLength] = 0; var sb = new U8StringBuilder(str); bool overflowed = sb.Append(path).Overflowed; diff --git a/src/LibHac/FsSystem/FsPath.cs b/src/LibHac/FsSystem/FsPath.cs index b5e24dd3..e11c8c84 100644 --- a/src/LibHac/FsSystem/FsPath.cs +++ b/src/LibHac/FsSystem/FsPath.cs @@ -27,7 +27,7 @@ namespace LibHac.FsSystem Unsafe.SkipInit(out fsPath); // Ensure null terminator even if the creation fails for safety - fsPath.Str[0x301] = 0; + fsPath.Str[MaxLength] = 0; var sb = new U8StringBuilder(fsPath.Str); bool overflowed = sb.Append(path).Overflowed; diff --git a/src/LibHac/InternalsVisibleToTests.cs b/src/LibHac/InternalsVisibleToTests.cs new file mode 100644 index 00000000..5efd5916 --- /dev/null +++ b/src/LibHac/InternalsVisibleToTests.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("LibHac.Tests")] diff --git a/src/LibHac/Sm/ServiceManager.cs b/src/LibHac/Sm/ServiceManager.cs index 2bd5ae31..23872cb6 100644 --- a/src/LibHac/Sm/ServiceManager.cs +++ b/src/LibHac/Sm/ServiceManager.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using LibHac.Common; -using LibHac.Sf; +using LibHac.Svc; namespace LibHac.Sm { @@ -22,7 +22,7 @@ namespace LibHac.Sm if (!Services.TryGetValue(serviceName, out IServiceObject service)) { - return ResultSf.RequestDeferredByUser.Log(); + return ResultSvc.NotFound.Log(); } return service.GetServiceObject(out serviceObject); diff --git a/src/LibHac/Svc/ResultSvc.cs b/src/LibHac/Svc/ResultSvc.cs new file mode 100644 index 00000000..25806c7b --- /dev/null +++ b/src/LibHac/Svc/ResultSvc.cs @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// This file was automatically generated. +// Changes to this file will be lost when the file is regenerated. +// +// To change this file, modify /build/CodeGen/results.csv at the root of this +// repo and run the build script. +// +// The script can be run with the "codegen" option to run only the +// code generation portion of the build. +//----------------------------------------------------------------------------- + +namespace LibHac.Svc +{ + public static class ResultSvc + { + public const int ModuleSvc = 1; + + /// Error code: 2001-0007; Inner value: 0xe01 + public static Result.Base OutOfSessions => new Result.Base(ModuleSvc, 7); + /// Error code: 2001-0014; Inner value: 0x1c01 + public static Result.Base InvalidArgument => new Result.Base(ModuleSvc, 14); + /// Error code: 2001-0033; Inner value: 0x4201 + public static Result.Base NotImplemented => new Result.Base(ModuleSvc, 33); + /// Error code: 2001-0054; Inner value: 0x6c01 + public static Result.Base StopProcessingException => new Result.Base(ModuleSvc, 54); + /// Error code: 2001-0057; Inner value: 0x7201 + public static Result.Base NoSynchronizationObject => new Result.Base(ModuleSvc, 57); + /// Error code: 2001-0059; Inner value: 0x7601 + public static Result.Base TerminationRequested => new Result.Base(ModuleSvc, 59); + /// Error code: 2001-0070; Inner value: 0x8c01 + public static Result.Base NoEvent => new Result.Base(ModuleSvc, 70); + /// Error code: 2001-0101; Inner value: 0xca01 + public static Result.Base InvalidSize => new Result.Base(ModuleSvc, 101); + /// Error code: 2001-0102; Inner value: 0xcc01 + public static Result.Base InvalidAddress => new Result.Base(ModuleSvc, 102); + /// Error code: 2001-0103; Inner value: 0xce01 + public static Result.Base OutOfResource => new Result.Base(ModuleSvc, 103); + /// Error code: 2001-0104; Inner value: 0xd001 + public static Result.Base OutOfMemory => new Result.Base(ModuleSvc, 104); + /// Error code: 2001-0105; Inner value: 0xd201 + public static Result.Base OutOfHandles => new Result.Base(ModuleSvc, 105); + /// Error code: 2001-0106; Inner value: 0xd401 + public static Result.Base InvalidCurrentMemory => new Result.Base(ModuleSvc, 106); + /// Error code: 2001-0108; Inner value: 0xd801 + public static Result.Base InvalidNewMemoryPermission => new Result.Base(ModuleSvc, 108); + /// Error code: 2001-0110; Inner value: 0xdc01 + public static Result.Base InvalidMemoryRegion => new Result.Base(ModuleSvc, 110); + /// Error code: 2001-0112; Inner value: 0xe001 + public static Result.Base InvalidPriority => new Result.Base(ModuleSvc, 112); + /// Error code: 2001-0113; Inner value: 0xe201 + public static Result.Base InvalidCoreId => new Result.Base(ModuleSvc, 113); + /// Error code: 2001-0114; Inner value: 0xe401 + public static Result.Base InvalidHandle => new Result.Base(ModuleSvc, 114); + /// Error code: 2001-0115; Inner value: 0xe601 + public static Result.Base InvalidPointer => new Result.Base(ModuleSvc, 115); + /// Error code: 2001-0116; Inner value: 0xe801 + public static Result.Base InvalidCombination => new Result.Base(ModuleSvc, 116); + /// Error code: 2001-0117; Inner value: 0xea01 + public static Result.Base TimedOut => new Result.Base(ModuleSvc, 117); + /// Error code: 2001-0118; Inner value: 0xec01 + public static Result.Base Cancelled => new Result.Base(ModuleSvc, 118); + /// Error code: 2001-0119; Inner value: 0xee01 + public static Result.Base OutOfRange => new Result.Base(ModuleSvc, 119); + /// Error code: 2001-0120; Inner value: 0xf001 + public static Result.Base InvalidEnumValue => new Result.Base(ModuleSvc, 120); + /// Error code: 2001-0121; Inner value: 0xf201 + public static Result.Base NotFound => new Result.Base(ModuleSvc, 121); + /// Error code: 2001-0122; Inner value: 0xf401 + public static Result.Base Busy => new Result.Base(ModuleSvc, 122); + /// Error code: 2001-0123; Inner value: 0xf601 + public static Result.Base SessionClosed => new Result.Base(ModuleSvc, 123); + /// Error code: 2001-0124; Inner value: 0xf801 + public static Result.Base NotHandled => new Result.Base(ModuleSvc, 124); + /// Error code: 2001-0125; Inner value: 0xfa01 + public static Result.Base InvalidState => new Result.Base(ModuleSvc, 125); + /// Error code: 2001-0126; Inner value: 0xfc01 + public static Result.Base ReservedUsed => new Result.Base(ModuleSvc, 126); + /// Error code: 2001-0127; Inner value: 0xfe01 + public static Result.Base NotSupported => new Result.Base(ModuleSvc, 127); + /// Error code: 2001-0128; Inner value: 0x10001 + public static Result.Base Debug => new Result.Base(ModuleSvc, 128); + /// Error code: 2001-0129; Inner value: 0x10201 + public static Result.Base NoThread => new Result.Base(ModuleSvc, 129); + /// Error code: 2001-0130; Inner value: 0x10401 + public static Result.Base UnknownThread => new Result.Base(ModuleSvc, 130); + /// Error code: 2001-0131; Inner value: 0x10601 + public static Result.Base PortClosed => new Result.Base(ModuleSvc, 131); + /// Error code: 2001-0132; Inner value: 0x10801 + public static Result.Base LimitReached => new Result.Base(ModuleSvc, 132); + /// Error code: 2001-0133; Inner value: 0x10a01 + public static Result.Base InvalidMemoryPool => new Result.Base(ModuleSvc, 133); + /// Error code: 2001-0258; Inner value: 0x20401 + public static Result.Base ReceiveListBroken => new Result.Base(ModuleSvc, 258); + /// Error code: 2001-0259; Inner value: 0x20601 + public static Result.Base OutOfAddressSpace => new Result.Base(ModuleSvc, 259); + /// Error code: 2001-0260; Inner value: 0x20801 + public static Result.Base MessageTooLarge => new Result.Base(ModuleSvc, 260); + /// Error code: 2001-0517; Inner value: 0x40a01 + public static Result.Base InvalidProcessId => new Result.Base(ModuleSvc, 517); + /// Error code: 2001-0518; Inner value: 0x40c01 + public static Result.Base InvalidThreadId => new Result.Base(ModuleSvc, 518); + /// Error code: 2001-0519; Inner value: 0x40e01 + public static Result.Base InvalidId => new Result.Base(ModuleSvc, 519); + /// Error code: 2001-0520; Inner value: 0x41001 + public static Result.Base ProcessTerminated => new Result.Base(ModuleSvc, 520); + } +} diff --git a/tests/LibHac.Tests/FsSrv/AccessControlTests.cs b/tests/LibHac.Tests/FsSrv/AccessControlTests.cs new file mode 100644 index 00000000..db942ea4 --- /dev/null +++ b/tests/LibHac.Tests/FsSrv/AccessControlTests.cs @@ -0,0 +1,64 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.FsSrv.Impl; +using LibHac.Ncm; +using Xunit; +using ContentType = LibHac.Fs.ContentType; + +namespace LibHac.Tests.FsSrv +{ + public class AccessControlTests + { + [Fact] + public void OpenFileSystemWithNoPermissions_ReturnsPermissionDenied() + { + Horizon hos = HorizonFactory.CreateBasicHorizon(); + + HorizonClient regClient = hos.CreatePrivilegedHorizonClient(); + HorizonClient client = hos.CreateHorizonClient(); + + var dataHeader = new AccessControlDataHeader(); + var descriptor = new AccessControlDescriptor(); + + descriptor.Version = 123; + dataHeader.Version = 123; + + descriptor.AccessFlags = (ulong)AccessControlBits.Bits.None; + dataHeader.AccessFlags = (ulong)AccessControlBits.Bits.None; + + Assert.Success(regClient.Fs.RegisterProgram(client.ProcessId.Value, new ProgramId(123), + StorageId.BuiltInUser, SpanHelpers.AsReadOnlyByteSpan(in dataHeader), + SpanHelpers.AsReadOnlyByteSpan(in descriptor))); + + Result rc = client.Fs.MountContent("test".ToU8Span(), "@System:/fake.nca".ToU8Span(), ContentType.Control); + Assert.Result(ResultFs.PermissionDenied, rc); + } + + [Fact] + public void OpenFileSystemWithPermissions_ReturnsInvalidNcaMountPoint() + { + Horizon hos = HorizonFactory.CreateBasicHorizon(); + + HorizonClient regClient = hos.CreatePrivilegedHorizonClient(); + HorizonClient client = hos.CreateHorizonClient(); + + var dataHeader = new AccessControlDataHeader(); + var descriptor = new AccessControlDescriptor(); + + descriptor.Version = 123; + dataHeader.Version = 123; + + descriptor.AccessFlags = (ulong)AccessControlBits.Bits.ApplicationInfo; + dataHeader.AccessFlags = (ulong)AccessControlBits.Bits.ApplicationInfo; + + Assert.Success(regClient.Fs.RegisterProgram(client.ProcessId.Value, new ProgramId(123), + StorageId.BuiltInUser, SpanHelpers.AsReadOnlyByteSpan(in dataHeader), + SpanHelpers.AsReadOnlyByteSpan(in descriptor))); + + // We should get InvalidNcaMountPoint because mounting NCAs from @System isn't allowed + Result rc = client.Fs.MountContent("test".ToU8Span(), "@System:/fake.nca".ToU8Span(), ContentType.Control); + Assert.Result(ResultFs.InvalidNcaMountPoint, rc); + } + } +} diff --git a/tests/LibHac.Tests/HorizonFactory.cs b/tests/LibHac.Tests/HorizonFactory.cs new file mode 100644 index 00000000..a580ecb8 --- /dev/null +++ b/tests/LibHac.Tests/HorizonFactory.cs @@ -0,0 +1,25 @@ +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSrv; + +namespace LibHac.Tests +{ + public static class HorizonFactory + { + public static Horizon CreateBasicHorizon() + { + IFileSystem rootFs = new InMemoryFileSystem(); + + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, new Keyset()); + + var config = new FileSystemServerConfig(); + config.FsCreators = defaultObjects.FsCreators; + config.DeviceOperator = defaultObjects.DeviceOperator; + config.ExternalKeySet = new ExternalKeySet(); + + var horizon = new Horizon(new StopWatchTimeSpanGenerator(), config); + + return horizon; + } + } +}