Skip to content

Commit 6814687

Browse files
committed
Make reading of ACLs optional, since it may disrupt other processes
1 parent 33ead94 commit 6814687

9 files changed

Lines changed: 169 additions & 63 deletions

File tree

Native.cs

Lines changed: 66 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,71 @@ private static T PtrToStruct<T>(IntPtr p)
107107
{
108108
return (T)Marshal.PtrToStructure(p, typeof(T));
109109
}
110+
111+
public static AclModel GetAcl(string path)
112+
{
113+
var err = GetNamedSecurityInfo(path, SE_OBJECT_TYPE.SE_FILE_OBJECT,
114+
SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION | SECURITY_INFORMATION.GROUP_SECURITY_INFORMATION | SECURITY_INFORMATION.DACL_SECURITY_INFORMATION,
115+
out var ownerSid, out var groupSid, out var dacl, out _, out var sd);
116+
if (!err.Succeeded)
117+
return null;
118+
119+
try
120+
{
121+
int ownerNameBufLen = 1024, groupNameBufLen = 1024, domainBufLen = 1024;
122+
StringBuilder ownerNameBuf = new StringBuilder(ownerNameBufLen);
123+
StringBuilder groupNameBuf = new StringBuilder(groupNameBufLen);
124+
StringBuilder domainBuf = new StringBuilder(domainBufLen);
125+
LookupAccountSid(null, ownerSid, ownerNameBuf, ref ownerNameBufLen, domainBuf, ref domainBufLen, out var ownerAccType);
126+
LookupAccountSid(null, groupSid, groupNameBuf, ref groupNameBufLen, null, ref domainBufLen, out var groupAccType);
127+
128+
List<AclRuleModel> rules = null;
129+
if (dacl.IsValidAcl())
130+
{
131+
var cnt = dacl.AceCount();
132+
rules = new List<AclRuleModel>((int)cnt);
133+
for (uint i = 0; i < cnt; i++)
134+
{
135+
if (GetAce(dacl, i, out var ace))
136+
{
137+
var sid = ace.GetSid();
138+
int sidNameLen = 1024, sidDomainLen = 1024;
139+
StringBuilder sidNameBuf = new StringBuilder(sidNameLen);
140+
StringBuilder sidDomainBuf = new StringBuilder(sidDomainLen);
141+
LookupAccountSid(null, sid, sidNameBuf, ref sidNameLen, sidDomainBuf, ref sidDomainLen, out var sidAccType);
142+
143+
bool isAllowing;
144+
switch (ace.GetHeader().AceType)
145+
{
146+
case AceType.AccessAllowed:
147+
isAllowing = true;
148+
break;
149+
case AceType.AccessDenied:
150+
isAllowing = false;
151+
break;
152+
default:
153+
continue;
154+
}
155+
156+
var mask = ace.GetMask();
157+
// make Enum formatter happy, since there are no flags for 0x60 bits
158+
mask &= 0xFFFFFF9F;
159+
160+
if (sidNameBuf.Length > 0)
161+
rules.Add(new AclRuleModel(sidNameBuf.ToString(), isAllowing, (PipeAccessRights)mask));
162+
}
163+
}
164+
}
165+
166+
return new AclModel(ownerNameBuf.ToString(), groupNameBuf.ToString(), rules);
167+
}
168+
finally
169+
{
170+
sd.Dispose();
171+
}
172+
}
110173

111-
public static IEnumerable<PipeModel> GetPipes(string pipeHost = ".")
174+
public static IEnumerable<PipeModel> GetPipes(bool readAcls, string pipeHost = ".")
112175
{
113176
IntPtr dir, tmp;
114177
bool isFirstQuery = true;
@@ -140,65 +203,8 @@ public static IEnumerable<PipeModel> GetPipes(string pipeHost = ".")
140203
var name = Marshal.PtrToStringUni(namePtr, (int)fdi.FileNameLength / 2);
141204

142205
AclModel acl = null;
143-
var err = GetNamedSecurityInfo(pipesPath + name, SE_OBJECT_TYPE.SE_FILE_OBJECT,
144-
SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION | SECURITY_INFORMATION.GROUP_SECURITY_INFORMATION | SECURITY_INFORMATION.DACL_SECURITY_INFORMATION,
145-
out var ownerSid, out var groupSid, out var dacl, out _, out var sd);
146-
if (err.Succeeded)
147-
{
148-
try
149-
{
150-
int ownerNameBufLen = 1024, groupNameBufLen = 1024, domainBufLen = 1024;
151-
StringBuilder ownerNameBuf = new StringBuilder(ownerNameBufLen);
152-
StringBuilder groupNameBuf = new StringBuilder(groupNameBufLen);
153-
StringBuilder domainBuf = new StringBuilder(domainBufLen);
154-
LookupAccountSid(null, ownerSid, ownerNameBuf, ref ownerNameBufLen, domainBuf, ref domainBufLen, out var ownerAccType);
155-
LookupAccountSid(null, groupSid, groupNameBuf, ref groupNameBufLen, null, ref domainBufLen, out var groupAccType);
156-
157-
List<AclRuleModel> rules = null;
158-
if (dacl.IsValidAcl())
159-
{
160-
var cnt = dacl.AceCount();
161-
rules = new List<AclRuleModel>((int)cnt);
162-
for (uint i = 0; i < cnt; i++)
163-
{
164-
if (GetAce(dacl, i, out var ace))
165-
{
166-
var sid = ace.GetSid();
167-
int sidNameLen = 1024, sidDomainLen = 1024;
168-
StringBuilder sidNameBuf = new StringBuilder(sidNameLen);
169-
StringBuilder sidDomainBuf = new StringBuilder(sidDomainLen);
170-
LookupAccountSid(null, sid, sidNameBuf, ref sidNameLen, sidDomainBuf, ref sidDomainLen, out var sidAccType);
171-
172-
bool isAllowing;
173-
switch (ace.GetHeader().AceType)
174-
{
175-
case AceType.AccessAllowed:
176-
isAllowing = true;
177-
break;
178-
case AceType.AccessDenied:
179-
isAllowing = false;
180-
break;
181-
default:
182-
continue;
183-
}
184-
185-
var mask = ace.GetMask();
186-
// make Enum formatter happy, since there are no flags for 0x60 bits
187-
mask &= 0xFFFFFF9F;
188-
189-
if (sidNameBuf.Length > 0)
190-
rules.Add(new AclRuleModel(sidNameBuf.ToString(), isAllowing, (PipeAccessRights)mask));
191-
}
192-
}
193-
}
194-
195-
acl = new AclModel(ownerNameBuf.ToString(), groupNameBuf.ToString(), rules);
196-
}
197-
finally
198-
{
199-
sd.Dispose();
200-
}
201-
}
206+
if (readAcls)
207+
acl = GetAcl(pipesPath + name);
202208

203209
yield return new PipeModel(pipeHost, name, (int)fdi.AllocationSize.LowPart, fdi.EndOfFile.LowPart, acl);
204210

PipeExplorerMainWindow.xaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -->
6969
Width="400"
7070
>
7171
<StackPanel Margin="12">
72+
<metro:ToggleSwitch IsOn="{Binding ReadAcls}"
73+
Header="{x:Static res:Resources.SettingsReadAclsHeader}"
74+
OnContent="{x:Static res:Resources.SettingsReadAclsOn}"
75+
OffContent="{x:Static res:Resources.SettingsReadAclsOff}"
76+
/>
77+
<TextBlock Foreground="DarkOrange" Text="{x:Static res:Resources.SettingsReadAclsWarning}" TextWrapping="Wrap" Margin="0, 0, 0, 8" />
78+
7279
<metro:ToggleSwitch IsOn="{Binding StartImmediately}"
7380
Header="{x:Static res:Resources.SettingsStartImmediatelyHeader}"
7481
OnContent="{x:Static res:Resources.SettingsStartImmediatelyOn}"

Properties/Resources.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Properties/Resources.en-US.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,18 @@
174174
<data name="SettingsHighlightDuration" xml:space="preserve">
175175
<value>Highlight duration</value>
176176
</data>
177+
<data name="SettingsReadAclsHeader" xml:space="preserve">
178+
<value>Read access control lists (ACL). Does not work for busy pipes.</value>
179+
</data>
180+
<data name="SettingsReadAclsOff" xml:space="preserve">
181+
<value>Do not read</value>
182+
</data>
183+
<data name="SettingsReadAclsOn" xml:space="preserve">
184+
<value>Read</value>
185+
</data>
186+
<data name="SettingsReadAclsWarning" xml:space="preserve">
187+
<value>Warning: reading access control lists may disrupt workflows of processes willing to connect to named pipes, including workflows of system services.</value>
188+
</data>
177189
<data name="SettingsRefreshInterval" xml:space="preserve">
178190
<value>Refresh interval</value>
179191
</data>

Properties/Resources.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,18 @@
174174
<data name="SettingsHighlightDuration" xml:space="preserve">
175175
<value>Highlight duration</value>
176176
</data>
177+
<data name="SettingsReadAclsHeader" xml:space="preserve">
178+
<value>Read access control lists (ACL). Does not work for busy pipes.</value>
179+
</data>
180+
<data name="SettingsReadAclsOff" xml:space="preserve">
181+
<value>Do not read</value>
182+
</data>
183+
<data name="SettingsReadAclsOn" xml:space="preserve">
184+
<value>Read</value>
185+
</data>
186+
<data name="SettingsReadAclsWarning" xml:space="preserve">
187+
<value>Warning: reading access control lists may disrupt workflows of processes willing to connect to named pipes, including workflows of system services.</value>
188+
</data>
177189
<data name="SettingsRefreshInterval" xml:space="preserve">
178190
<value>Refresh interval</value>
179191
</data>

Properties/Resources.ru.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,18 @@
174174
<data name="SettingsHighlightDuration" xml:space="preserve">
175175
<value>Длительность подсветки</value>
176176
</data>
177+
<data name="SettingsReadAclsHeader" xml:space="preserve">
178+
<value>Считывать списки контроля доступа (ACL). Не работает для занятых каналов.</value>
179+
</data>
180+
<data name="SettingsReadAclsOff" xml:space="preserve">
181+
<value>Не считывать</value>
182+
</data>
183+
<data name="SettingsReadAclsOn" xml:space="preserve">
184+
<value>Считывать</value>
185+
</data>
186+
<data name="SettingsReadAclsWarning" xml:space="preserve">
187+
<value>Предупреждение: считывание параметров доступа может нарушить поведение процессов, подключающихся к именованным каналам, в том числе поведение системных служб.</value>
188+
</data>
177189
<data name="SettingsRefreshInterval" xml:space="preserve">
178190
<value>Интервал обновления</value>
179191
</data>

Services/PipeWatcher.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using System.Threading;
2323
using PipeExplorer.Models;
2424
using ReactiveUI;
25+
using Splat;
2526

2627
namespace PipeExplorer.Services
2728
{
@@ -143,7 +144,8 @@ public override string ToString()
143144
private void TimerTick(object state)
144145
{
145146
var newPipes = new Dictionary<string, PipeModel>();
146-
foreach (var newp in Native.GetPipes(Host))
147+
var readAcls = Locator.Current.GetService<ISettings>().ReadAcls;
148+
foreach (var newp in Native.GetPipes(readAcls, Host))
147149
{
148150
// do not blindly call newPipes.Add(): GetPipes() could return duplicated entries (and it's not its fault)
149151
if (prevPipes.TryGetValue(newp.Name, out var oldp))

Services/Settings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ interface ISettings : IReactiveObject
4242
TimeSpan HighlightDuration { get; set; }
4343
TimeSpan RefreshInterval { get; set; }
4444
bool StartImmediately { get; set; }
45+
bool ReadAcls { get; set; }
4546
WindowState WindowState { get; set; }
4647
Rect WindowPosition { get; set; }
4748
IDictionary<string, int> ColumnWidths { get; }
@@ -57,6 +58,7 @@ class Settings : ReactiveValidationObject<Settings>, ISettings
5758
[Reactive] public TimeSpan HighlightDuration { get; set; } = TimeSpan.FromSeconds(4); // sec
5859
[Reactive] public TimeSpan RefreshInterval { get; set; } = TimeSpan.FromSeconds(1); // sec
5960
[Reactive] public bool StartImmediately { get; set; } = true;
61+
[Reactive] public bool ReadAcls { get; set; } = false;
6062
[Reactive] public WindowState WindowState { get; set; } = WindowState.Normal;
6163
[Reactive] public Rect WindowPosition { get; set; }
6264
public ObservableDictionary<string, int> ColumnWidths { get; } = new ObservableDictionary<string, int>();
@@ -100,6 +102,8 @@ private void Load()
100102
RefreshInterval = TimeSpan.FromSeconds(interval);
101103
if (regKey.GetValue("Start immediately", 1) is int n)
102104
StartImmediately = n != 0;
105+
if (regKey.GetValue("Read ACLs", 0) is int n2)
106+
ReadAcls = n2 != 0;
103107
if (regKey.GetValue("Window state") is string stateStr && Enum.TryParse<WindowState>(stateStr, true, out var state))
104108
WindowState = state;
105109

@@ -163,6 +167,7 @@ public async void Save()
163167
regKey.SetValue("Highlight duration", (int)HighlightDuration.TotalSeconds, RegistryValueKind.DWord);
164168
regKey.SetValue("Refresh interval", (int)RefreshInterval.TotalSeconds, RegistryValueKind.DWord);
165169
regKey.SetValue("Start immediately", StartImmediately, RegistryValueKind.DWord);
170+
regKey.SetValue("Read ACLs", ReadAcls, RegistryValueKind.DWord);
166171
regKey.SetValue("Window state", WindowState.ToString(), RegistryValueKind.String);
167172

168173
if (WindowState == WindowState.Normal)

ViewModels/PipeExplorerViewModel.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ public bool StartImmediately
5454
}
5555
}
5656

57+
public bool ReadAcls
58+
{
59+
get => settings.ReadAcls;
60+
set
61+
{
62+
if (value != settings.ReadAcls)
63+
{
64+
this.RaisePropertyChanging();
65+
settings.ReadAcls = value;
66+
this.RaisePropertyChanged();
67+
}
68+
}
69+
}
70+
5771
public int RefreshInterval
5872
{
5973
get => (int)settings.RefreshInterval.TotalSeconds;
@@ -145,7 +159,7 @@ public PipeExplorerViewModel()
145159

146160
pipesCache
147161
.Connect()
148-
.Batch(TimeSpan.FromMilliseconds(300))
162+
.Batch(TimeSpan.FromMilliseconds(50))
149163
.Filter(this.WhenValueChanged(x => x.QuickFilter).Select(BuildFilter))
150164
.AutoRefresh(p => p.Pinned)
151165
.Sort(SortExpressionComparer<PipeViewModel>.Ascending(p => p))
@@ -163,7 +177,7 @@ public PipeExplorerViewModel()
163177
.Select(v => (ImageSource)App.Current.FindResource(v ? "AppIconActive" : "AppIcon"))
164178
.Subscribe(v => AppIcon = v);
165179

166-
pipesCache.AddOrUpdate(Native.GetPipes().Select(p => new PipeViewModel(p, settings.PinnedNames.Contains(p.Name), false)));
180+
pipesCache.AddOrUpdate(Native.GetPipes(ReadAcls).Select(p => new PipeViewModel(p, settings.PinnedNames.Contains(p.Name), false)));
167181
IsRunning = settings.StartImmediately;
168182
}
169183

0 commit comments

Comments
 (0)