Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 58 additions & 8 deletions src/System.Management.Automation/help/CabinetNativeApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,17 @@ internal static IntPtr FdiOpen(string filename, int oflag, int pmode)
{
return new IntPtr(-1);
}
catch (Exception e) when (e is UnauthorizedAccessException ||
e is ArgumentException ||
e is NotSupportedException ||
e is System.Security.SecurityException)
{
// FdiOpen is used both as a reverse-P/Invoke callback (invoked by the native
// FDI engine) and as a direct call from FdiNotify to create the destination file.
// In either path it must never throw across native FDI frames; return -1 to
// signal open failure, matching the IOException handler above.
return new IntPtr(-1);
}
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
Expand Down Expand Up @@ -315,18 +326,27 @@ internal static int FdiWrite(IntPtr fp, byte[] buffer, int count)

internal static int FdiClose(IntPtr fp)
{
GCHandle handle = GCHandle.FromIntPtr(fp);
FileStream stream = (FileStream)handle.Target;

if (stream == null)
if (fp == IntPtr.Zero || fp == new IntPtr(-1))
{
return -1;
}
else

GCHandle handle = GCHandle.FromIntPtr(fp);
try
{
FileStream stream = (FileStream)handle.Target;
if (stream == null)
{
return -1;
}

stream.Dispose();
return 0;
}
catch (IOException)
{
return -1;
}
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
Expand Down Expand Up @@ -364,6 +384,13 @@ internal static int FdiSeek(IntPtr fp, int offset, int origin)
/// <exception cref="InvalidOperationException">Thrown when path validation fails.</exception>
private static string ValidateExtractionPath(string helpDirectory, string entryPath)
{
// Reject empty CAB entry names that would otherwise resolve to the help directory root.
if (string.IsNullOrEmpty(entryPath))
{
throw new InvalidOperationException(
$"CAB entry contains an empty path: '{entryPath}'");
}

// Reject absolute paths, UNC paths, or rooted paths in CAB entries
if (Path.IsPathRooted(entryPath))
{
Expand Down Expand Up @@ -401,6 +428,13 @@ private static string ValidateExtractionPath(string helpDirectory, string entryP
$"CAB entry path is invalid. Entry '{entryPath}' would extract to '{resolvedPath}' which is outside the intended directory '{canonicalHelpDir}'");
}

// Reject entries that resolve exactly to the help directory root (e.g. empty entries).
if (resolvedPath.Length <= canonicalHelpDir.Length)
{
throw new InvalidOperationException(
$"CAB entry '{entryPath}' resolves to the help directory root '{canonicalHelpDir}'");
}

return resolvedPath;
}

Expand Down Expand Up @@ -443,7 +477,16 @@ e is IOException ||
case FdiNotificationType.FdintCLOSE_FILE_INFO:
{
// Close the file
CabinetNativeApi.FdiClose(fdin.hf);
int closeResult = CabinetNativeApi.FdiClose(fdin.hf);

// FDI's FNFDINOTIFY contract uses FALSE (0) to signal a CLOSE_FILE_INFO
// failure. A non-zero FdiClose result here means the final flush/Dispose
// failed (e.g. disk-full / I/O error), leaving a truncated file on disk;
// abort this entry rather than reporting a corrupt extraction as success.
if (closeResult != 0)
{
return IntPtr.Zero;
Comment on lines +486 to +488
}

// Get the intended help directory
string helpDirectory = Marshal.PtrToStringAnsi(fdin.pv);
Expand All @@ -466,7 +509,6 @@ e is PathTooLongException ||

try
{
// Set the file attributes
IntPtr hFile = PlatformInvokes.CreateFile(
absoluteFilePath,
PlatformInvokes.FileDesiredAccess.GenericRead | PlatformInvokes.FileDesiredAccess.GenericWrite,
Expand All @@ -476,7 +518,10 @@ e is PathTooLongException ||
PlatformInvokes.FileAttributes.Normal,
IntPtr.Zero);

if (hFile != IntPtr.Zero)
// CreateFile returns INVALID_HANDLE_VALUE (-1) on failure, not Zero.
bool validHandle = hFile != IntPtr.Zero && hFile != new IntPtr(-1);

if (validHandle)
{
PlatformInvokes.FILETIME ftFile = new PlatformInvokes.FILETIME();
if (PlatformInvokes.DosDateTimeToFileTime(fdin.date, fdin.time, ftFile))
Expand Down Expand Up @@ -506,6 +551,11 @@ e is UnauthorizedAccessException ||
// Call notification function
return new IntPtr(1);
}
case FdiNotificationType.FdintNEXT_CABINET:
{
// Update-Help content is always a single self-contained cabinet.
return new IntPtr(-1);
}
}

return new IntPtr(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,17 @@ private void ProcessSingleModuleObject(PSModuleInfo module, ExecutionContext con
return;
}

if (module.Name.Length > UpdatableHelpModuleInfo.MaxModuleNameLength)
{
if (!noErrors)
{
ProcessException(module.Name, null, new UpdatableHelpSystemException(
"ModuleNameTooLong", StringUtil.Format(HelpDisplayStrings.ModuleNameTooLong, module.Name.Length, UpdatableHelpModuleInfo.MaxModuleNameLength),
ErrorCategory.InvalidArgument, null, null));
}
return;
}

var keyTuple2 = new Tuple<string, Version>(module.Name, module.Version);
if (!helpModules.ContainsKey(keyTuple2))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ internal class UpdatableHelpModuleInfo
#endif
internal static readonly string HelpIntoXmlName = "HelpInfo.xml";

// Defensive upper bound on the module name length. The module name flows
// unmodified into generated help file names (GetHelpContentName /
// GetHelpInfoName) and, via ModuleBase (...\Modules\<name>\<version>\), can
// appear more than once in a resolved path. This is a defensive cap on the
// caller-influenced component; it does not by itself guarantee the full path
// stays within any specific OS limit (long paths are honored when enabled).
internal const int MaxModuleNameLength = 128;

/// <summary>
/// Class constructor.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@
<data name="ModuleNameNullOrEmpty" xml:space="preserve">
<value>The following parameter must not be null or empty: Module.</value>
</data>
<data name="ModuleNameTooLong" xml:space="preserve">
<value>The module name exceeds the maximum supported length of {1} characters (actual length: {0}).</value>
</data>
<data name="PathNullOrEmpty" xml:space="preserve">
<value>The following parameter must not be null or empty: Path.</value>
</data>
Expand Down
Loading