Skip to content

fix: ipc input validation - #2852

Open
NickVs2015 wants to merge 7 commits into
devfrom
fix/ipc-input-validation
Open

fix: ipc input validation#2852
NickVs2015 wants to merge 7 commits into
devfrom
fix/ipc-input-validation

Conversation

@NickVs2015

@NickVs2015 NickVs2015 commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

fix ipc input validation during test

- Validate IP/CIDR values from IPC before passing to Linux firewall
- Replace shell interpolation with direct execve in firewall update functions
- Block dangerous OpenVPN/WireGuard arguments in sanitizeArguments()
- Add programId bounds check in IpcServerProcess::setProgram()
- Add SO_PEERCRED peer authentication for IPC connections on Linux
@NickVs2015
NickVs2015 requested a review from vkamn July 20, 2026 20:08
@NickVs2015 NickVs2015 changed the title fix: ipc input validation and valnurability fix: ipc input validation and fix linux Jul 20, 2026
@NickVs2015
NickVs2015 force-pushed the fix/ipc-input-validation branch from cacf1d4 to 35acbad Compare July 21, 2026 11:45
@NickVs2015 NickVs2015 changed the title fix: ipc input validation and fix linux fix: ipc input validation Jul 21, 2026
@NickVs2015
NickVs2015 changed the base branch from dev to feat/locations_change July 21, 2026 17:24
@NickVs2015
NickVs2015 changed the base branch from feat/locations_change to dev July 21, 2026 17:25
Comment thread client/platforms/linux/daemon/linuxfirewall.cpp
Comment thread client/platforms/linux/daemon/linuxfirewall.cpp
Comment thread client/platforms/linux/daemon/linuxfirewall.cpp
Comment thread ipc/ipc.h Outdated
Tun2Socks,
CertUtil
CertUtil,
_Count

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This exposes _Count to the whole ::amnezia namespace. Consider calling it something like PermittedProcessCount, or make PermittedProcess a enum class

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

ok, this lines was changed to PermittedProcessCount like you advise

Comment thread ipc/ipc.h Outdated

switch (proc) {
case OpenVPN: {
static const QSet<QString> blocked = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blacklist strategy here is weaker than whitelisting IMO.
We have a very strict case of usage of OpenVPN, and we decide which args to call from the client application.
Let's put here only the fields which are actually used in client. It would be also useful to reuse the existing validation code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

switch to whitelisting strategy

Comment thread ipc/ipc.h Outdated
return out;
}
case Wireguard: {
static const QRegularExpression hookRe(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same here. Use whitelisting strategy instead of blacklisting

Comment thread service/server/localserver.cpp Outdated
uid_t g_allowedUid = static_cast<uid_t>(-1);
bool g_allowedUidSet = false;

static bool checkPeerCredentials(QLocalSocket *socket) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What's the point of this feature?
Theoretically, when the main app crashes - this thingy on the server keeps the previous UID. So it will not pass any other connections.
Client does not have any handles of restarting the service manually - so it will be stuck till manual restart or system reset

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

in that condition ,scenario with different UIDs is practically impossible in real usage (the service runs as root, the client runs as the user), and a client crash does not change its UID on restart — the same user will reconnect with the same UID, so the service will not get stuck. But i agree that it can be discussed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, nevermind, I thought I saw pid here.

What's the point limiting multiple UIDs? I don't think that's a vector of attack IMO. Moreover, it uses the 1st one as "allowed", which opens a new exploits window - an attacker could try to connect to the IPC before the actual client.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

revert it, but it potencial risk which was describe in independed valnurable research

Comment thread service/server/killswitch.cpp Outdated
return true;
}

static QStringList filterIpList(const QStringList &values) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think filtering-out invalid values is a bugprone way in here. Let's just print an error and return if any of them is not valid.
Modernize it: use std::all_of for this task, or similar

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

ok, change it

Comment thread service/server/killswitch.cpp Outdated
static QStringList validateIpList(const QStringList &values) {
if (!std::all_of(values.cbegin(), values.cend(), isValidIpOrCidr)) {
qWarning() << "IPC: IP list contains invalid value, rejecting entire list";
return {};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

That's weird reporting the warning which is actually critical, and do nothing in this case.
Why don't we check all the fields at the start of specific calls?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

moved it

Comment thread ipc/ipc.h Outdated
// Whitelist only args actually used by the client:
// --config <path>, --management <host> <port>, --management-client
QStringList out;
for (int i = 0; i < args.size(); ++i) {

@ygurov ygurov Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There is a mechanism of validation, which is already implemented. Why do we need the same thingy twice?
Check tun2socks implementation below

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

reused existed code

Comment thread ipc/ipcserverprocess.cpp Outdated

void IpcServerProcess::setProgram(int programId)
{
if (programId <= static_cast<int>(amnezia::PermittedProcess::Invalid) ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think these checks are making the code to look complex without a reason, and they do nothing actually.

  1. If the error could happen returned - tell the caller about it. There is no point of logging the error without the actual error, program does not know what to do with it
  2. This call is used only by GUI application (and attackers maybe), so there is no check required IMO. You can also do asserts in here instead, to highlight an error for the developer

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

add assert

@NickVs2015
NickVs2015 requested a review from ygurov August 6, 2026 10:33
Comment thread ipc/ipc.h Outdated
}
case Wireguard: {
// Whitelist only subcommand + config file path (wg-quick up/down <conf>)
if (args.size() == 2) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  1. Validator does not really care which args sequence does user provide - it's the problem of wg-quick itself. This is to be handled by wg-quick side.
  2. We do not use Wireguard in our app, and (I think) do not provide any execs of those names. Could be a vector of attack

So, I would recommend neither:

  • To remove WireGuard out of here completely
  • Or to use the same positional/named args in here as well as for Tun2Socks, for example

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

removed it

Comment thread ipc/ipc.h Outdated

switch (proc) {
case OpenVPN: {
// Whitelist only args actually used by the client:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please keep the code clean from the slope comments. They do not provide any information - it is written below

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

done

Comment thread ipc/ipcserver.cpp Outdated
extern uid_t g_allowedUid;
extern bool g_allowedUidSet;

static bool checkPrivPeerCredentials(QLocalSocket *socket) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same question as for the WireGuard's localServer daemon. This feature is doubtful

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

removed

Comment thread service/server/killswitch.cpp
Comment thread service/server/killswitch.cpp Outdated
break;
}
dnsServers.append(dns.toString());
const QString dnsStr = dns.toString();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would recommend to implement this check at the top of the method (basically, near the ranges check), so you would not repeat yourself for each OSes specifically

Comment thread service/server/localserver.cpp Outdated
QObject::connect(m_server.data(), &QLocalServer::newConnection, this, [this]() {
qDebug() << "LocalServer new connection";
m_serverNode.addHostSideConnection(m_server->nextPendingConnection());
QLocalSocket *conn = m_server->nextPendingConnection();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What's the difference? Why do we change this

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

resolve it

@NickVs2015
NickVs2015 requested a review from ygurov August 6, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants