Skip to content

Commit e02c2b9

Browse files
committed
feat(resolve): harden strict library resolution model
1 parent 2ceb8b7 commit e02c2b9

6 files changed

Lines changed: 219 additions & 85 deletions

File tree

File renamed without changes.

Consyzer/Core/Models/LibraryResolutionResult.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ internal enum MechanismKind
4646
ApplicationDirectory = 1,
4747
DefaultSystemLocations = 2,
4848
EnvironmentOverride = 3,
49-
PlatformSpecificMechanism = 4
49+
PlatformSpecificMechanism = 4,
50+
CurrentDirectory = 5
5051
}
5152

5253
[Flags]

Consyzer/Core/Resolvers/Platform/LinuxLibraryResolutionResolver.cs

Lines changed: 73 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
namespace Consyzer.Core.Resolvers.Platform;
44

5-
internal sealed class LinuxLibraryResolutionResolver(string analyzedDirectory)
6-
: PlatformLibraryResolutionResolverBase
5+
internal sealed class LinuxLibraryResolutionResolver(
6+
string analyzedDirectory
7+
) : PlatformLibraryResolutionResolverBase
78
{
9+
private const string LibraryExtension = ".so";
10+
private const string EnvironmentVariablePath = "LD_LIBRARY_PATH";
11+
812
private readonly string _analyzedDirectory = Path.GetFullPath(analyzedDirectory);
913

1014
// Linux has a lot of mechanisms we can't simulate.
@@ -14,7 +18,6 @@ internal sealed class LinuxLibraryResolutionResolver(string analyzedDirectory)
1418
| NotSimulatedMechanisms.LinuxLdSoConf
1519
| NotSimulatedMechanisms.LinuxSecureExecution
1620
| NotSimulatedMechanisms.LinuxTransitiveDependencies
17-
| NotSimulatedMechanisms.LinuxMultiarchDefaultPaths
1821
| NotSimulatedMechanisms.LinuxLdPreload;
1922

2023
private static readonly string[] DefaultSystemLocations =
@@ -23,46 +26,59 @@ internal sealed class LinuxLibraryResolutionResolver(string analyzedDirectory)
2326
"/usr/lib",
2427
"/lib64",
2528
"/usr/lib64",
26-
"/usr/local/lib"
29+
"/usr/local/lib",
30+
"/lib/x86_64-linux-gnu",
31+
"/usr/lib/x86_64-linux-gnu",
32+
"/lib/aarch64-linux-gnu",
33+
"/usr/lib/aarch64-linux-gnu",
34+
"/lib/arm-linux-gnueabihf",
35+
"/usr/lib/arm-linux-gnueabihf"
2736
];
2837

2938
public override LibraryResolutionResult Resolve(string file)
3039
{
31-
var normalized = NormalizeLibraryName(file);
32-
33-
// TO DO: make state-machine
40+
var candidates = GetLibraryNameCandidates(file);
41+
var heuristicCandidates = IsExplicitPath(candidates[0])
42+
? []
43+
: CollectHeuristicCandidates(candidates);
3444

35-
if (TryResolveExplicit(file, normalized, out var result)) return result;
36-
if (TryResolveLdLibraryPath(file, normalized, out result)) return result;
37-
if (TryResolveDefaultSystemLocations(file, normalized, out result)) return result;
45+
if (TryResolveExplicit(file, candidates[0], heuristicCandidates, out var result)) return result;
46+
if (TryResolveLdLibraryPath(file, candidates, heuristicCandidates, out result)) return result;
47+
if (TryResolveDefaultSystemLocations(file, candidates, heuristicCandidates, out result)) return result;
3848

39-
return CreateInconclusive(file, CollectHeuristicCandidates(normalized), NotSimulated);
49+
return CreateInconclusive(file, heuristicCandidates, NotSimulated);
4050
}
4151

42-
private static bool TryResolveExplicit(string requestedName, string normalized, out LibraryResolutionResult result)
52+
private static bool TryResolveExplicit(
53+
string requestedName,
54+
string normalizedExplicitPath,
55+
IReadOnlyList<string> heuristicCandidates,
56+
out LibraryResolutionResult result
57+
)
4358
{
44-
if (!TryGetExplicitPathCandidate(normalized, out var candidate))
59+
if (!TryGetExplicitPathCandidate(normalizedExplicitPath, out var candidate))
4560
{
4661
result = default!;
4762
return false;
4863
}
4964

5065
result = candidate is not null
51-
? CreateResolved(requestedName, candidate, MechanismKind.ExplicitPath)
52-
: CreateMissing(requestedName);
66+
? CreateResolved(requestedName, candidate, MechanismKind.ExplicitPath, heuristicCandidates)
67+
: CreateMissing(requestedName, heuristicCandidates);
5368

5469
return true;
5570
}
5671

5772
private static bool TryResolveLdLibraryPath(
58-
string requestedName,
59-
string normalized,
73+
string requestedName,
74+
IReadOnlyList<string> candidates,
75+
IReadOnlyList<string> heuristicCandidates,
6076
out LibraryResolutionResult result
6177
)
6278
{
6379
var candidate = TryResolveInDirectories(
64-
normalized,
65-
SplitSearchPath(Environment.GetEnvironmentVariable("LD_LIBRARY_PATH"))
80+
candidates,
81+
SplitSearchPath(Environment.GetEnvironmentVariable(EnvironmentVariablePath))
6682
);
6783

6884
if (candidate is null)
@@ -71,50 +87,66 @@ out LibraryResolutionResult result
7187
return false;
7288
}
7389

74-
result = CreateResolved(requestedName, candidate, MechanismKind.EnvironmentOverride);
90+
result = CreateResolved(requestedName, candidate, MechanismKind.EnvironmentOverride, heuristicCandidates);
7591
return true;
7692
}
7793

7894
private static bool TryResolveDefaultSystemLocations(
79-
string requestedName,
80-
string normalized,
95+
string requestedName,
96+
IReadOnlyList<string> candidates,
97+
IReadOnlyList<string> heuristicCandidates,
8198
out LibraryResolutionResult result
8299
)
83100
{
84-
var candidate = TryResolveInDirectories(normalized, DefaultSystemLocations);
101+
var candidate = TryResolveInDirectories(candidates, DefaultSystemLocations);
85102
if (candidate is null)
86103
{
87104
result = default!;
88105
return false;
89106
}
90107

91-
result = CreateResolved(requestedName, candidate, MechanismKind.DefaultSystemLocations);
108+
result = CreateResolved(requestedName, candidate, MechanismKind.DefaultSystemLocations, heuristicCandidates);
92109
return true;
93110
}
94111

95-
private static string NormalizeLibraryName(string input)
112+
private static IReadOnlyList<string> GetLibraryNameCandidates(string input)
96113
{
97-
// Keep as-is if extension exists or contains ".so" somewhere (covers "libm.so.6" too).
98-
if (Path.HasExtension(input) || input.Contains(".so", StringComparison.Ordinal))
114+
if (IsExplicitPath(input))
115+
{
116+
return [input];
117+
}
118+
119+
if (EndsWithSharedObjectName(input))
99120
{
100-
return input;
121+
var extensioned = input + LibraryExtension;
122+
return DistinctCandidates(
123+
[
124+
input,
125+
WithLibPrefix(input),
126+
extensioned,
127+
WithLibPrefix(extensioned)
128+
], StringComparer.Ordinal);
101129
}
102130

103-
var extensioned = Path.ChangeExtension(input, "so");
131+
var canonical = input + LibraryExtension;
104132

105-
return input.StartsWith("lib", StringComparison.Ordinal)
106-
? extensioned
107-
: "lib" + extensioned;
133+
return DistinctCandidates(
134+
[
135+
canonical,
136+
WithLibPrefix(canonical),
137+
input,
138+
WithLibPrefix(input)
139+
], StringComparer.Ordinal);
108140
}
109141

110-
private string[] CollectHeuristicCandidates(string normalized)
111-
{
112-
string? a = GetCandidatePath(_analyzedDirectory, normalized);
113-
string? b = GetCandidatePath(Directory.GetCurrentDirectory(), normalized);
142+
private static bool EndsWithSharedObjectName(string input)
143+
=> input.EndsWith(LibraryExtension, StringComparison.Ordinal)
144+
|| input.Contains(LibraryExtension, StringComparison.Ordinal);
114145

115-
if (a is null && b is null) return [];
116-
if (a is null) return [b!];
117-
if (b is null) return [a];
118-
return [a, b];
146+
private static string WithLibPrefix(string input) => "lib" + input;
147+
148+
private IReadOnlyList<string> CollectHeuristicCandidates(IReadOnlyList<string> candidates)
149+
{
150+
return CollectCandidatePaths(candidates, _analyzedDirectory, Directory.GetCurrentDirectory());
119151
}
120-
}
152+
}

Consyzer/Core/Resolvers/Platform/PlatformLibraryResolutionResolverBase.cs

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Consyzer.Core.Models;
2+
using Consyzer.Helpers;
23

34
namespace Consyzer.Core.Resolvers.Platform;
45

@@ -22,6 +23,28 @@ internal abstract class PlatformLibraryResolutionResolverBase
2223
return null;
2324
}
2425

26+
protected static string? TryResolveInDirectories(
27+
IReadOnlyList<string> fileNames,
28+
IEnumerable<string?> directories
29+
)
30+
{
31+
foreach (var dir in directories)
32+
{
33+
if (string.IsNullOrWhiteSpace(dir)) continue;
34+
35+
foreach (var fileName in fileNames)
36+
{
37+
var candidate = GetCandidatePath(dir, fileName);
38+
if (candidate is not null)
39+
{
40+
return candidate;
41+
}
42+
}
43+
}
44+
45+
return null;
46+
}
47+
2548
protected static string? GetCandidatePath(string? baseDirectory, string file)
2649
{
2750
var candidate = string.IsNullOrWhiteSpace(baseDirectory)
@@ -31,12 +54,37 @@ internal abstract class PlatformLibraryResolutionResolverBase
3154
return File.Exists(candidate) ? Path.GetFullPath(candidate) : null;
3255
}
3356

57+
protected static IReadOnlyList<string> CollectCandidatePaths(
58+
IReadOnlyList<string> fileNames,
59+
params string?[] directories
60+
)
61+
{
62+
var candidates = new List<string>();
63+
var seen = new HashSet<string>(PlatformStringComparisonHelper.FilePathComparer);
64+
65+
foreach (var dir in directories)
66+
{
67+
if (string.IsNullOrWhiteSpace(dir)) continue;
68+
69+
foreach (var fileName in fileNames)
70+
{
71+
var candidate = GetCandidatePath(dir, fileName);
72+
if (candidate is not null && seen.Add(candidate))
73+
{
74+
candidates.Add(candidate);
75+
}
76+
}
77+
}
78+
79+
return candidates;
80+
}
81+
3482
protected static IEnumerable<string> SplitSearchPath(string? path)
3583
{
3684
if (string.IsNullOrWhiteSpace(path)) yield break;
3785

3886
var parts = path.Split(
39-
Path.PathSeparator,
87+
Path.PathSeparator,
4088
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
4189
);
4290

@@ -64,24 +112,28 @@ protected static bool TryGetExplicitPathCandidate(string path, out string? candi
64112
protected static LibraryResolutionResult CreateResolved(
65113
string requestedName,
66114
string path,
67-
MechanismKind kind
115+
MechanismKind kind,
116+
IReadOnlyList<string>? heuristicCandidates = null
68117
)
69118
=> new()
70119
{
71120
LibraryName = requestedName,
72121
State = ResolutionState.Resolved,
73122
Resolved = new ResolvedPresence(path, kind),
74-
HeuristicCandidates = [],
123+
HeuristicCandidates = heuristicCandidates ?? [],
75124
NotSimulated = NotSimulatedMechanisms.None
76125
};
77126

78-
protected static LibraryResolutionResult CreateMissing(string requestedName)
127+
protected static LibraryResolutionResult CreateMissing(
128+
string requestedName,
129+
IReadOnlyList<string>? heuristicCandidates = null
130+
)
79131
=> new()
80132
{
81133
LibraryName = requestedName,
82134
State = ResolutionState.Missing,
83135
Resolved = null,
84-
HeuristicCandidates = [],
136+
HeuristicCandidates = heuristicCandidates ?? [],
85137
NotSimulated = NotSimulatedMechanisms.None
86138
};
87139

@@ -99,8 +151,27 @@ NotSimulatedMechanisms notSimulated
99151
NotSimulated = notSimulated
100152
};
101153

102-
protected static bool IsExplicitPath(string path)
103-
=> Path.IsPathRooted(path)
154+
protected static bool IsExplicitPath(string path)
155+
=> Path.IsPathRooted(path)
104156
|| path.Contains(Path.DirectorySeparatorChar)
105157
|| path.Contains(Path.AltDirectorySeparatorChar);
158+
159+
protected static IReadOnlyList<string> DistinctCandidates(
160+
IEnumerable<string> candidates,
161+
StringComparer comparer
162+
)
163+
{
164+
var result = new List<string>();
165+
var seen = new HashSet<string>(comparer);
166+
167+
foreach (var candidate in candidates)
168+
{
169+
if (seen.Add(candidate))
170+
{
171+
result.Add(candidate);
172+
}
173+
}
174+
175+
return result;
176+
}
106177
}

0 commit comments

Comments
 (0)