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
2 changes: 1 addition & 1 deletion docs/linux-port/threat-model-launching.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Attacker: controls a file's name, content, mode bits and symlinks (downloaded ar
| 1 | Decision from file bytes (ELF / `#!` magic), not name or MIME glob | `OpenDecision.Sniff/Decide` | `OpenDecisionTests` |
| 2 | Binaries (+x) always ask Run/Cancel with full path; scripts (+x) ask Run/Display/Cancel; ELF without +x is refused | `OpenDecision`, `ExecutePlanAsync` | `ContentDecidesNeverTheName` |
| 3 | `.desktop`: silent only if under an XDG `applications` dir; otherwise always ask showing the exact argv; +x is not trust | `OpenDecision`, `ExecutePlanAsync` | `Desktop_Trusted...` |
| 4 | Strict `.desktop` parse: one `[Desktop Entry]` group, no duplicate keys, Type+Exec required, no NUL/control chars in file or Exec, no localized `Exec[..]/Type[..]/Terminal[..]/Path[..]/TryExec[..]`; invalid = refused (never given to a handler) | `DesktopEntryParser.ParseStrict` | `StrictParse_*` |
| 4 | Strict `.desktop` parse: NUL rejected anywhere in the file; control chars rejected inside `[Desktop Entry]` (including whitespace-padded group headers such as `\v[Foo]`) and in Exec; one `[Desktop Entry]` group, no duplicate keys, Type+Exec required, no localized `Exec[..]/Type[..]/Terminal[..]/Path[..]/TryExec[..]`; on the launcher path other groups and malformed lines are ignored, service menus stay strict everywhere; invalid = refused (never given to a handler) | `DesktopEntryParser.ParseStrict` | `StrictParse_*` |
| 5 | Display equals exec: the argv is computed once (`DesktopExecExpander.Expand`), shown (control chars escaped), and passed unchanged to `ILauncherService.RunCommandAsync`; nothing is re-parsed or re-expanded after the dialog | `LinuxOpenPlan.Argv`, `RunCommandAsync` | `RunCommand_StartsExactlyTheDisplayedArgv...`, `Exec_ParsedOnce...` |
| 6 | Dialog answer maps to exactly the described follow-up; Cancel/close/dialog failure never opens anything. "Display" opens the file in an explicitly chosen `text/plain` handler, never the file's own MIME default | `OpenDecision.Resolve`, `DisplayAsTextAsync` | `DialogAnswerMapsToExactlyTheDescribedFollowUp` |
| 7 | TOCTOU: identity (dev, ino, mode, size, mtime; statx without following the final symlink) captured before the dialog and re-checked after it and before every start, including shared default-app launches | `FileIdentity`, `LinuxOpenPlan.StillValid` | `FileIdentity_DetectsChange` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,11 @@ private void ApplyAnchorOffset(double target, int request, int attempt)
OwnerPanel.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
if (request != offsetRequest || !IsGeometryCurrent || ScrollViewer is not { } viewer)
{
if (request == offsetRequest && ScrollViewer is null)
restoringAnchor = false;
return;
}
var extent = ScrollOrientation == Orientation.Vertical ? viewer.ExtentHeight : viewer.ExtentWidth;
if (Math.Abs(extent - Geometry.Extent) > 1 && attempt < 8)
{
Expand Down Expand Up @@ -403,7 +407,8 @@ public override Line CreateLine(GeneratorDirection fillDirection, double extentO
? new Rect(0, extentOffset, availableBreadth, cellExtent)
: new Rect(extentOffset, 0, cellExtent, availableBreadth));
OwnerPanel.InvalidateMeasure();
return new Line(Math.Max(0, flat), (placeholder, nextVisibleItem));
var clamped = Math.Clamp(flat, 0, Math.Max(0, Geometry.Count - 1));
return new Line(clamped, (placeholder, Uno.UI.IndexPath.FromRowSection(clamped, 0)));
}
var views = new (FrameworkElement container, Uno.UI.IndexPath index)[row.Count];
for (var column = 0; column < row.Count; column++)
Expand Down
6 changes: 6 additions & 0 deletions src/Files.Platform.Linux/Mime/DesktopEntryParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ public sealed record Entry(DesktopApplication Application, bool Hidden, string?
var line = rawLine.Trim();
if (desktopEntryOnly)
{
// Whitespace-padded group headers inside [Desktop Entry] must not hide control characters.
if (values is not null && line.StartsWith('[') && rawLine.TrimEnd('\r').Any(c => char.IsControl(c) && c != '\t'))
{
error = "control character";
return null;
}
if (line.StartsWith('[') && line != "[Desktop Entry]")
{
values = null;
Expand Down
2 changes: 2 additions & 0 deletions tests/Files.Platform.Tests/Launching/OpenDecisionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ public void StrictParse_IgnoresLinesWithoutKeys()
[DataRow("[Desktop Entry]\nType=Application\nName=X\nExec=safe\n[Desktop Action a]\nName=unused\0name\n")]
[DataRow("[Desktop Action a]\nName=unused\0name\n[Desktop Entry]\nType=Application\nName=X\nExec=safe\n")]
[DataRow("[Desktop Entry]\nType=Application\nName=X\nExec=a\n[Desktop Action a]\nExec=unused\n[Desktop Entry]\nExec=b\n")]
[DataRow("[Desktop Entry]\nType=Application\nName=X\nExec=safe\n\v[Foo]\nName=y\n")]
[DataRow("[Desktop Entry]\nType=Application\nName=X\nExec=safe\n[Foo]\f\nName=y\n")]
public void StrictParse_RejectsAmbiguousEntries(string text)
{
Assert.IsNull(Strict(text, out var error));
Expand Down
Loading