Skip to content
Open
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
34 changes: 29 additions & 5 deletions Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Microsoft.VisualBasic;
using PCL.Core.IO;
using PCL.Core.App;
using PCL.Core.App.Localization;
using PCL.Core.UI;
Expand Down Expand Up @@ -2845,8 +2846,7 @@ public static List<McLibToken> McLibListGetWithJson(JsonObject jsonObject,
init.Url = (string)(rootUrl ?? library["downloads"]["artifact"]["url"]),
init.LocalPath = library["downloads"]["artifact"]["path"] is null
? McLibGet((string)library["name"], customMcFolder: customMcFolder)
: Path.Combine(customMcFolder, "libraries", library["downloads"]["artifact"]["path"].ToString()
.Replace("/", @"\")),
: McLibGetSafeDownloadPath(customMcFolder, library["downloads"]["artifact"]["path"]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not swallow rejected artifact paths

When an untrusted manifest supplies a downloads.artifact.path that McLibGetSafeDownloadPath rejects, this call is still inside the broad try whose catch below adds a fallback token using localPath derived from the same manifest's name. That means rejected metadata does not stop processing; if the same library name contains traversal separators in its non-group fields, the downloader can still receive a path from McLibGet outside libraries, bypassing the new containment check. Let the IOException propagate or skip the library instead of falling back after a containment failure.

Useful? React with 👍 / 👎.

init.size = (long)Math.Round(
ModBase.Val(library["downloads"]["artifact"]["size"].ToString())),
init.IsNatives = false, init.Sha1 = library["downloads"]["artifact"]["sha1"]?.ToString(),
Expand Down Expand Up @@ -2885,9 +2885,8 @@ public static List<McLibToken> McLibListGetWithJson(JsonObject jsonObject,
? McLibGet((string)library["name"], customMcFolder: customMcFolder)
.Replace(".jar", "-" + library["natives"]["windows"] + ".jar")
.Replace("${arch}", Environment.Is64BitOperatingSystem ? "64" : "32")
: Path.Combine(customMcFolder, "libraries",
library["downloads"]["classifiers"]["natives-windows"]["path"].ToString()
.Replace("/", @"\")),
: McLibGetSafeDownloadPath(customMcFolder,
library["downloads"]["classifiers"]["natives-windows"]["path"]),
size = (long)Math.Round(
ModBase.Val(library["downloads"]["classifiers"]["natives-windows"]["size"].ToString())),
IsNatives = true,
Expand Down Expand Up @@ -3100,6 +3099,31 @@ public static List<DownloadFile> McLibNetFilesFromInstance(Instance instance)
return result;
}

/// <summary>
/// 根据下载元数据中的相对路径获取支持库下载地址,并阻止路径逃逸 libraries 文件夹。
/// </summary>
private static string McLibGetSafeDownloadPath(string customMcFolder, JsonNode pathNode)
{
var rawPath = pathNode?.ToString();
if (string.IsNullOrWhiteSpace(rawPath))
throw new IOException("支持库下载路径无效");

var normalizedPath = rawPath.Replace("/", @"\");
if (Path.IsPathRooted(normalizedPath) || normalizedPath.StartsWith(@"\") ||
Regex.IsMatch(normalizedPath, @"^[A-Za-z]:"))
throw new IOException($"支持库下载路径不能为绝对路径:{rawPath}");

if (normalizedPath.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries).Any(x => x == ".."))
throw new IOException($"支持库下载路径不能包含上级目录:{rawPath}");

var librariesFolder = Path.Combine(customMcFolder, "libraries");
var localPath = Path.GetFullPath(Path.Combine(librariesFolder, normalizedPath));
if (!Files.IsPathWithinDirectory(localPath, librariesFolder))
throw new IOException($"支持库下载路径不在 libraries 文件夹内:{rawPath}");

return localPath;
}

/// <summary>
/// 将 McLibToken 列表转换为 NetFile。
/// </summary>
Expand Down