Compare commits

..

3 Commits

Author SHA1 Message Date
NickVs2015
041cdf39b4 fix: suppress linux firewall errors and add missing 400.allowPIA anchor 2026-07-21 19:06:33 +03:00
NickVs2015
35acbadd43 fix: extend IPC security validation to macOS firewall 2026-07-20 22:34:20 +03:00
NickVs2015
5d774e6c38 fix: resolve critical IPC security vulnerabilities
- 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
2026-07-20 22:34:20 +03:00
34 changed files with 301 additions and 568 deletions

View File

@@ -531,8 +531,6 @@ jobs:
QT_INSTALL_DIR: ${{ runner.temp }}
CODESIGN_SIGNATURE: ${{ secrets.MAC_SIGNER_ID }}
CODESIGN_INSTALLER_SIGNATURE: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
CODESIGN_KEYCHAIN: ${{ steps.setup-keychain.outputs.keychain-path }}
CODESIGN_INSTALLER_KEYCHAIN: ${{ steps.setup-keychain.outputs.keychain-path }}
NOTARYTOOL_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
NOTARYTOOL_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
NOTARYTOOL_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}

View File

@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.9.0.7)
set(AMNEZIAVPN_VERSION 4.9.0.4)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2134)
set(APP_ANDROID_VERSION_CODE 2133)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

View File

@@ -38,14 +38,14 @@
## Links
- [https://amnezia.org](https://amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/amnezia/amnezia.org/utm_source=github&utm_campaign=amnezia_website-read)
- [https://docs.amnezia.org](https://docs.amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Documentation
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://telegram.me/amnezia_vpn_en](https://telegram.me/amnezia_vpn_en) - Telegram support channel (English)
- [https://telegram.me/amnezia_vpn_ir](https://telegram.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
- [https://telegram.me/amnezia_vpn_mm](https://telegram.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
- [https://telegram.me/amnezia_vpn](https://telegram.me/amnezia_vpn) - Telegram support channel (Russian)
- [Get Premium for 6 or 12 months](https://storage.googleapis.com/amnezia/pay/?utm_source=github&utm_campaign=ampay-read)
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium
## Tech

View File

@@ -35,14 +35,14 @@
## Ссылки
- [https://amnezia.org](https://amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/amnezia/amnezia.org/utm_source=github&utm_campaign=amnezia_website-read)
- [https://docs.amnezia.org](https://docs.amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Документация
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://telegram.me/amnezia_vpn_en](https://telegram.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
- [https://telegram.me/amnezia_vpn_ir](https://telegram.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
- [https://telegram.me/amnezia_vpn_mm](https://telegram.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
- [https://telegram.me/amnezia_vpn](https://telegram.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
- [Оформите Premium на 6 или 12 месяцев](https://storage.googleapis.com/amnezia/pay/?utm_source=github&utm_campaign=ampay-read)
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
## Технологии

View File

@@ -145,9 +145,6 @@ void AmneziaApplication::init()
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
m_marketplaceUpdateController.reset(new MarketplaceUpdateController());
m_marketplaceUpdateController->start();
m_engine->addImportPath("qrc:/ui/qml/Modules/");
if (m_parser.isSet(m_optImport)) {

View File

@@ -15,7 +15,6 @@
#include "core/controllers/coreController.h"
#include "secureQSettings.h"
#include "ui/controllers/marketplaceUpdateController.h"
#include "vpnConnection.h"
#include "ui/models/containerProps.h"
#include "ui/models/protocolProps.h"
@@ -57,7 +56,6 @@ private:
SecureQSettings* m_settings;
QScopedPointer<CoreController> m_coreController;
QScopedPointer<MarketplaceUpdateController> m_marketplaceUpdateController;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;

View File

@@ -3,13 +3,6 @@ package org.amnezia.vpn
import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.Dialog
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.view.Gravity
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import android.app.NotificationManager
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
@@ -107,8 +100,6 @@ class AmneziaActivity : QtActivity() {
private var pendingOpenFileUri: String? = null
private var openFileDeliveryScheduled = false
private var updateCoverDialog: Dialog? = null
private val vpnServiceEventHandler: Handler by lazy(NONE) {
object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
@@ -496,116 +487,6 @@ class AmneziaActivity : QtActivity() {
super.onDestroy()
}
fun showUpdateCover() {
runOnUiThread {
if (isFinishing || isDestroyed || updateCoverDialog != null) return@runOnUiThread
val dialog = Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen)
dialog.setCancelable(false)
val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER
setBackgroundColor(0xFF0E0E11.toInt())
}
dialog.setContentView(root)
dialog.show()
updateCoverDialog = dialog
}
}
fun hideUpdateCover() {
runOnUiThread {
updateCoverDialog?.dismiss()
updateCoverDialog = null
}
}
fun showUpdatePrompt(title: String, message: String, updateTitle: String, skipTitle: String, storeUrl: String) {
runOnUiThread {
if (isFinishing || isDestroyed) return@runOnUiThread
val dialog = updateCoverDialog ?: Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen).also {
it.setCancelable(false)
it.show()
updateCoverDialog = it
}
val density = resources.displayMetrics.density
fun dp(value: Int) = (value * density).toInt()
val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER
setBackgroundColor(0xFF0E0E11.toInt())
setPadding(dp(32), dp(32), dp(32), dp(32))
}
val titleView = TextView(this).apply {
text = title
textSize = 22f
setTextColor(0xFFFFFFFF.toInt())
gravity = Gravity.CENTER
typeface = Typeface.create(typeface, Typeface.BOLD)
}
val messageView = TextView(this).apply {
text = message
textSize = 16f
setTextColor(0xFFC7C8CB.toInt())
gravity = Gravity.CENTER
setPadding(0, dp(16), 0, dp(28))
}
val updateButton = Button(this).apply {
text = updateTitle
isAllCaps = false
textSize = 17f
setTextColor(0xFF0E0E11.toInt())
stateListAnimator = null
background = GradientDrawable().apply {
cornerRadius = dp(12).toFloat()
setColor(0xFFFBB26A.toInt())
}
setOnClickListener {
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(storeUrl)))
} catch (e: ActivityNotFoundException) {
Log.w(TAG, "open store failed: ${e.message}")
}
hideUpdateCover()
}
}
val skipButton = Button(this).apply {
text = skipTitle
isAllCaps = false
textSize = 17f
setTextColor(0xFFD7D8DB.toInt())
stateListAnimator = null
background = GradientDrawable().apply {
cornerRadius = dp(12).toFloat()
setColor(0x00000000)
setStroke(dp(1), 0xFF2C2D30.toInt())
}
setOnClickListener { hideUpdateCover() }
}
val updateParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, dp(52)
).apply { topMargin = dp(8) }
val skipParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, dp(52)
).apply { topMargin = dp(12) }
root.addView(titleView)
root.addView(messageView)
root.addView(updateButton, updateParams)
root.addView(skipButton, skipParams)
dialog.setContentView(root)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
Log.d(TAG, "Process activity result, code: ${actionCodeToString(requestCode)}, " +
"resultCode: $resultCode, data: $data")

View File

@@ -133,11 +133,6 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
)
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy

View File

@@ -31,6 +31,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
connect(this, &ConnectionController::killSwitchModeChangedRequested, m_vpnConnection, &VpnConnection::onKillSwitchModeChanged, Qt::QueuedConnection);
#ifdef Q_OS_ANDROID
connect(this, &ConnectionController::restoreConnectionRequested, m_vpnConnection, &VpnConnection::restoreConnection, Qt::QueuedConnection);
@@ -44,7 +45,9 @@ bool ConnectionController::isConnected() const
void ConnectionController::setConnectionState(Vpn::ConnectionState state)
{
emit connectionStateChanged(state);
if (m_vpnConnection) {
emit setConnectionStateRequested(state);
}
}
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const

View File

@@ -67,6 +67,7 @@ signals:
void connectionStateChanged(Vpn::ConnectionState state);
void openConnectionRequested(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration);
void closeConnectionRequested();
void setConnectionStateRequested(Vpn::ConnectionState state);
void killSwitchModeChangedRequested(bool enabled);
#ifdef Q_OS_ANDROID

View File

@@ -285,10 +285,6 @@ void CoreController::initSignalHandlers()
if (m_serversUiController->hasServersFromGatewayApi()) {
m_apiNewsUiController->fetchNews(false);
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_updateController->checkForUpdates();
#endif
}
void CoreController::updateTranslator(const QLocale &locale)

View File

@@ -440,6 +440,9 @@ void CoreSignalHandlers::initNotificationHandler()
void CoreSignalHandlers::initUpdateFoundHandler()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
connect(m_coreController->m_apiNewsUiController, &ApiNewsUiController::fetchNewsFinished, m_coreController->m_updateUiController,
&UpdateUiController::checkForUpdates);
connect(m_coreController->m_updateUiController, &UpdateUiController::updateFound, this, [this]() {
const QString version = m_coreController->m_updateUiController->getVersion();
const QString updateId = version.isEmpty() ? QStringLiteral("update") : QStringLiteral("update-%1").arg(version);

View File

@@ -64,7 +64,7 @@ namespace amnezia
constexpr char defaultFlow[] = "xtls-rprx-vision";
constexpr char defaultTransport[] = "raw";
constexpr char defaultFingerprint[] = "chrome";
constexpr char defaultSni[] = "www.googletagmanager.com";
constexpr char defaultSni[] = "cdn.example.com";
constexpr char defaultAlpn[] = "HTTP/2";
constexpr char defaultXhttpMode[] = "Auto";

View File

@@ -44,20 +44,8 @@
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<dict/>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>

View File

@@ -154,28 +154,6 @@ void AndroidController::resetLastServer(int serverIndex)
callActivityMethod("resetLastServer", "(I)V", serverIndex);
}
void AndroidController::showUpdateCover()
{
callActivityMethod("showUpdateCover", "()V");
}
void AndroidController::hideUpdateCover()
{
callActivityMethod("hideUpdateCover", "()V");
}
void AndroidController::showUpdatePrompt(const QString &title, const QString &message, const QString &updateTitle,
const QString &skipTitle, const QString &storeUrl)
{
callActivityMethod("showUpdatePrompt",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
QJniObject::fromString(title).object<jstring>(),
QJniObject::fromString(message).object<jstring>(),
QJniObject::fromString(updateTitle).object<jstring>(),
QJniObject::fromString(skipTitle).object<jstring>(),
QJniObject::fromString(storeUrl).object<jstring>());
}
void AndroidController::saveFile(const QString &fileName, const QString &data)
{
callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V",

View File

@@ -56,11 +56,6 @@ public:
bool requestAuthentication();
void sendTouch(float x, float y);
void showUpdateCover();
void hideUpdateCover();
void showUpdatePrompt(const QString &title, const QString &message, const QString &updateTitle,
const QString &skipTitle, const QString &storeUrl);
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);

View File

@@ -80,11 +80,6 @@ public:
void requestInetAccess();
bool isTestFlight();
void showUpdateCover();
void hideUpdateCover();
void showUpdatePrompt(const QString &title, const QString &message, const QString &updateTitle,
const QString &skipTitle, const QString &storeUrl);
signals:
void connectionStateChanged(Vpn::ConnectionState state);
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);

View File

@@ -1200,138 +1200,3 @@ bool IosController::isTestFlight() {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
return receiptURL && [[receiptURL lastPathComponent] isEqualToString:@"sandboxReceipt"];
}
#if !MACOS_NE
static UIWindow *s_updateCoverWindow = nil;
static UIWindowScene *activeWindowScene() {
UIWindowScene *fallback = nil;
for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
if (![scene isKindOfClass:[UIWindowScene class]]) {
continue;
}
fallback = (UIWindowScene *)scene;
if (scene.activationState == UISceneActivationStateForegroundActive) {
return (UIWindowScene *)scene;
}
}
return fallback;
}
#endif
void IosController::showUpdateCover() {
#if !MACOS_NE
void (^build)(void) = ^{
if (s_updateCoverWindow) {
return;
}
UIWindowScene *scene = activeWindowScene();
if (!scene) {
return;
}
UIWindow *win = [[UIWindow alloc] initWithWindowScene:scene];
win.windowLevel = UIWindowLevelAlert + 1;
UIViewController *vc = [[[UIViewController alloc] init] autorelease];
vc.view.backgroundColor = [UIColor colorWithRed:0.055 green:0.055 blue:0.063 alpha:1.0];
win.rootViewController = vc;
[win makeKeyAndVisible];
s_updateCoverWindow = win;
};
if ([NSThread isMainThread]) {
build();
} else {
dispatch_sync(dispatch_get_main_queue(), build);
}
#endif
}
void IosController::hideUpdateCover() {
#if !MACOS_NE
dispatch_async(dispatch_get_main_queue(), ^{
if (!s_updateCoverWindow) {
return;
}
s_updateCoverWindow.hidden = YES;
[s_updateCoverWindow release];
s_updateCoverWindow = nil;
});
#endif
}
void IosController::showUpdatePrompt(const QString &title, const QString &message, const QString &updateTitle,
const QString &skipTitle, const QString &storeUrl) {
#if !MACOS_NE
NSString *nsTitle = title.toNSString();
NSString *nsMessage = message.toNSString();
NSString *nsUpdate = updateTitle.toNSString();
NSString *nsSkip = skipTitle.toNSString();
NSString *nsUrl = storeUrl.toNSString();
dispatch_async(dispatch_get_main_queue(), ^{
if (!s_updateCoverWindow) {
return;
}
UIViewController *vc = s_updateCoverWindow.rootViewController;
void (^dismissCover)(void) = ^{
s_updateCoverWindow.hidden = YES;
[s_updateCoverWindow release];
s_updateCoverWindow = nil;
};
UILabel *titleLabel = [[[UILabel alloc] init] autorelease];
titleLabel.text = nsTitle;
titleLabel.font = [UIFont boldSystemFontOfSize:22];
titleLabel.textColor = [UIColor whiteColor];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.numberOfLines = 0;
UILabel *messageLabel = [[[UILabel alloc] init] autorelease];
messageLabel.text = nsMessage;
messageLabel.font = [UIFont systemFontOfSize:16];
messageLabel.textColor = [UIColor colorWithWhite:0.78 alpha:1.0];
messageLabel.textAlignment = NSTextAlignmentCenter;
messageLabel.numberOfLines = 0;
UIButton *updateButton = [UIButton buttonWithType:UIButtonTypeSystem];
[updateButton setTitle:nsUpdate forState:UIControlStateNormal];
[updateButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
updateButton.backgroundColor = [UIColor colorWithRed:1.0 green:0.6 blue:0.0 alpha:1.0];
updateButton.titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightSemibold];
updateButton.layer.cornerRadius = 12;
[updateButton.heightAnchor constraintEqualToConstant:52].active = YES;
[updateButton addAction:[UIAction actionWithHandler:^(__kindof UIAction *action) {
NSURL *url = [NSURL URLWithString:nsUrl];
if (url) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
}
dismissCover();
}] forControlEvents:UIControlEventTouchUpInside];
UIButton *skipButton = [UIButton buttonWithType:UIButtonTypeSystem];
[skipButton setTitle:nsSkip forState:UIControlStateNormal];
[skipButton setTitleColor:[UIColor colorWithWhite:0.7 alpha:1.0] forState:UIControlStateNormal];
skipButton.titleLabel.font = [UIFont systemFontOfSize:17];
[skipButton.heightAnchor constraintEqualToConstant:44].active = YES;
[skipButton addAction:[UIAction actionWithHandler:^(__kindof UIAction *action) {
dismissCover();
}] forControlEvents:UIControlEventTouchUpInside];
UIStackView *stack = [[[UIStackView alloc] initWithArrangedSubviews:@[titleLabel, messageLabel, updateButton, skipButton]] autorelease];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 16;
stack.translatesAutoresizingMaskIntoConstraints = NO;
[stack setCustomSpacing:28 afterView:messageLabel];
[vc.view addSubview:stack];
[NSLayoutConstraint activateConstraints:@[
[stack.centerYAnchor constraintEqualToAnchor:vc.view.centerYAnchor],
[stack.leadingAnchor constraintEqualToAnchor:vc.view.leadingAnchor constant:32],
[stack.trailingAnchor constraintEqualToAnchor:vc.view.trailingAnchor constant:-32]
]];
});
#else
Q_UNUSED(title) Q_UNUSED(message) Q_UNUSED(updateTitle) Q_UNUSED(skipTitle) Q_UNUSED(storeUrl)
#endif
}

View File

@@ -33,6 +33,7 @@
#include "linuxfirewall.h"
#include "logger.h"
#include "xray_defs.h"
#include <QFileInfo>
#include <QProcess>
#define BRAND_CODE "amn"
@@ -109,7 +110,7 @@ int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain,
// (we can't safely delete all rules at once since rule numbers change)
// TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when
// the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point..
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs -r %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
}
else
return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
@@ -291,6 +292,8 @@ void LinuxFirewall::install()
installAnchor(IPv4, QStringLiteral("110.allowNets"), {});
installAnchor(Both, QStringLiteral("400.allowPIA"), {});
installAnchor(Both, QStringLiteral("100.blockAll"), {
QStringLiteral("-j REJECT"),
});
@@ -454,16 +457,33 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers)
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName));
for (const QString& rule : getDNSRules(servers))
execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule));
const QString chain = QStringLiteral("%1.320.allowDNS").arg(kAnchorName);
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
const QStringList ifaces = {
QStringLiteral("amn0+"), QStringLiteral("tun0+"), QStringLiteral("tun2+")
};
for (const QString& server : servers) {
for (const QString& iface : ifaces) {
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-o"), iface,
QStringLiteral("-d"), server, QStringLiteral("-p"), QStringLiteral("udp"),
QStringLiteral("--dport"), QStringLiteral("53"), QStringLiteral("-j"), QStringLiteral("ACCEPT")});
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-o"), iface,
QStringLiteral("-d"), server, QStringLiteral("-p"), QStringLiteral("tcp"),
QStringLiteral("--dport"), QStringLiteral("53"), QStringLiteral("-j"), QStringLiteral("ACCEPT")});
}
}
}
void LinuxFirewall::updateAllowNets(const QStringList& servers)
{
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
for (const QString& rule : getAllowRule(servers))
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));
const QString chain = QStringLiteral("%1.110.allowNets").arg(kAnchorName);
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
for (const QString& server : servers)
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
QStringLiteral("-j"), QStringLiteral("ACCEPT")});
}
void LinuxFirewall::updateBlockNets(const QStringList& servers)
@@ -471,9 +491,12 @@ void LinuxFirewall::updateBlockNets(const QStringList& servers)
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.120.blockNets").arg(kAnchorName));
for (const QString& rule : getBlockRule(servers))
execute(QStringLiteral("iptables -A %1.120.blockNets %2").arg(kAnchorName, rule));
const QString chain = QStringLiteral("%1.120.blockNets").arg(kAnchorName);
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
for (const QString& server : servers)
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
QStringLiteral("-j"), QStringLiteral("REJECT")});
}
int waitForExitCode(QProcess& process)
@@ -506,10 +529,39 @@ int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
return exitCode;
}
int LinuxFirewall::executeIptables(const QString &program, const QStringList &args, bool ignoreErrors)
{
QProcess p;
p.start(program, args, QProcess::ReadOnly);
p.closeWriteChannel();
int exitCode = waitForExitCode(p);
auto out = p.readAllStandardOutput().trimmed();
auto err = p.readAllStandardError().trimmed();
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
logger.warning() << "(" << exitCode << ") $ " << program << args.join(QLatin1Char(' '));
if (!out.isEmpty())
logger.info() << out;
if (!err.isEmpty())
logger.warning() << err;
return exitCode;
}
void LinuxFirewall::setupTrafficSplitting()
{
const QString cgroupBase = QStringLiteral("/sys/fs/cgroup/net_cls");
if (!QFileInfo::exists(cgroupBase)) {
logger.warning() << "net_cls cgroup v1 not available, traffic splitting disabled";
return;
}
execute(QStringLiteral(
"if ! grep -qE '^[0-9]+[[:space:]]+%1$' /etc/iproute2/rt_tables 2>/dev/null ; then "
"echo '200 %1' >> /etc/iproute2/rt_tables ; fi"
).arg(kRtableName));
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
logger.info() << "Setting up cgroup in" << cGroupDir << "for traffic splitting";
execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId));
// Set a rule with priority 100 (lower priority than local but higher than main/default, 0 is highest priority)
execute(QStringLiteral("if ! ip rule list | grep -q %1 ; then ip rule add from all fwmark %1 lookup %2 pri 100 ; fi").arg(kPacketTag, kRtableName));
@@ -518,7 +570,7 @@ void LinuxFirewall::setupTrafficSplitting()
void LinuxFirewall::teardownTrafficSplitting()
{
logger.info() << "Tearing down cgroup and routing rules";
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1").arg(kRtableName));
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2>/dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1 2>/dev/null || true").arg(kRtableName));
execute(QStringLiteral("ip route flush cache"));
}

View File

@@ -85,6 +85,7 @@ private:
static void setupTrafficSplitting();
static void teardownTrafficSplitting();
static int execute(const QString& command, bool ignoreErrors = false);
static int executeIptables(const QString& program, const QStringList& args, bool ignoreErrors = false);
private:
// Chain names
static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain;

View File

@@ -29,7 +29,6 @@ void ConnectionUiController::openConnection()
{
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
return;
}

View File

@@ -1,163 +0,0 @@
#include "marketplaceUpdateController.h"
#include <QDebug>
#include "version.h"
#if defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLocale>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QRegularExpression>
#include <QVersionNumber>
#endif
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#endif
#if defined(Q_OS_ANDROID)
#include "platforms/android/android_controller.h"
#endif
#if defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
namespace
{
#if defined(Q_OS_IOS)
constexpr auto kIosBundleId = "org.amnezia.AmneziaVPN";
constexpr auto kIosStoreUrlFallback = "itms-apps://itunes.apple.com/app/id1600529900";
#else
constexpr auto kAndroidPackage = "org.amnezia.vpn";
constexpr auto kAndroidStoreUrl = "https://play.google.com/store/apps/details?id=org.amnezia.vpn";
#endif
} // namespace
#endif
MarketplaceUpdateController::MarketplaceUpdateController(QObject *parent) : QObject(parent)
{
}
void MarketplaceUpdateController::start()
{
#if defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
// Cover the app immediately so the store UI is not visible before the check
// resolves (avoids a flash of the main window before the update screen).
showCover();
const QUrl url = versionSourceUrl();
if (!url.isValid()) {
hideCover();
return;
}
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
request.setHeader(QNetworkRequest::UserAgentHeader, QByteArrayLiteral("AmneziaVPN"));
QNetworkReply *reply = m_nam.get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "[MarketplaceUpdate] network error:" << reply->errorString();
hideCover();
return;
}
QString version;
QString storeUrl;
if (!parseStoreVersion(reply->readAll(), version, storeUrl) || version.isEmpty()) {
qWarning() << "[MarketplaceUpdate] could not determine store version";
hideCover();
return;
}
const auto current = QVersionNumber::fromString(QString(APP_VERSION)).normalized();
const auto store = QVersionNumber::fromString(version).normalized();
qInfo() << "[MarketplaceUpdate] current:" << current.toString() << "store:" << store.toString();
if (store > current) {
showUpdatePrompt(storeUrl);
} else {
hideCover();
}
});
#endif
}
#if defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
QUrl MarketplaceUpdateController::versionSourceUrl() const
{
#if defined(Q_OS_IOS)
const QString country = QLocale::system().name().section('_', 1, 1).toLower();
QString url = QStringLiteral("https://itunes.apple.com/lookup?bundleId=%1").arg(kIosBundleId);
if (!country.isEmpty()) {
url += QStringLiteral("&country=%1").arg(country);
}
return QUrl(url);
#else
return QUrl(QStringLiteral("https://play.google.com/store/apps/details?id=%1&hl=en&gl=US").arg(kAndroidPackage));
#endif
}
bool MarketplaceUpdateController::parseStoreVersion(const QByteArray &body, QString &version, QString &storeUrl)
{
#if defined(Q_OS_IOS)
const auto results = QJsonDocument::fromJson(body).object().value("results").toArray();
if (results.isEmpty()) {
return false;
}
const auto first = results.first().toObject();
version = first.value("version").toString();
storeUrl = first.value("trackViewUrl").toString();
if (storeUrl.isEmpty()) {
storeUrl = QString::fromLatin1(kIosStoreUrlFallback);
}
return !version.isEmpty();
#else
const QString html = QString::fromUtf8(body);
static const QRegularExpression re(QStringLiteral("\\[\\[\\[\"(\\d+\\.\\d+(?:\\.\\d+){0,2})\"\\]\\]"));
const auto match = re.match(html);
if (match.hasMatch()) {
version = match.captured(1);
}
storeUrl = QString::fromLatin1(kAndroidStoreUrl);
return !version.isEmpty();
#endif
}
void MarketplaceUpdateController::showCover()
{
#if defined(Q_OS_IOS)
IosController::Instance()->showUpdateCover();
#else
AndroidController::instance()->showUpdateCover();
#endif
}
void MarketplaceUpdateController::hideCover()
{
#if defined(Q_OS_IOS)
IosController::Instance()->hideUpdateCover();
#else
AndroidController::instance()->hideUpdateCover();
#endif
}
void MarketplaceUpdateController::showUpdatePrompt(const QString &storeUrl)
{
const QString title = tr("Update available");
const QString message = tr("A new version of AmneziaVPN is available.");
const QString updateTitle = tr("Update");
const QString skipTitle = tr("Skip");
#if defined(Q_OS_IOS)
IosController::Instance()->showUpdatePrompt(title, message, updateTitle, skipTitle, storeUrl);
#else
AndroidController::instance()->showUpdatePrompt(title, message, updateTitle, skipTitle, storeUrl);
#endif
}
#endif

View File

@@ -1,30 +0,0 @@
#ifndef MARKETPLACEUPDATECONTROLLER_H
#define MARKETPLACEUPDATECONTROLLER_H
#include <QNetworkAccessManager>
#include <QObject>
#include <QUrl>
class MarketplaceUpdateController : public QObject
{
Q_OBJECT
public:
explicit MarketplaceUpdateController(QObject *parent = nullptr);
public slots:
void start();
private:
#if defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
QUrl versionSourceUrl() const;
bool parseStoreVersion(const QByteArray &body, QString &version, QString &storeUrl);
void showCover();
void hideCover();
void showUpdatePrompt(const QString &storeUrl);
QNetworkAccessManager m_nam;
#endif
};
#endif // MARKETPLACEUPDATECONTROLLER_H

View File

@@ -635,7 +635,6 @@ void InstallUiController::validateConfig()
{
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
emit configValidated(false);
return;
}
m_installController->validateConfig(serverId);

View File

@@ -4,7 +4,7 @@ import QtQuick.Controls
Menu {
property var textObj
popupType: Popup.Native
popupType: Qt.platform.os === "ios" ? Popup.Item : Popup.Native
property Item inputBlocker: null

View File

@@ -93,7 +93,7 @@ Rectangle {
wrapMode: Text.Wrap
ContextMenu.menu: contextMenu
ContextMenu.menu: Qt.platform.os === "ios" ? null : contextMenu
ContextMenuType {
id: contextMenu

View File

@@ -79,7 +79,7 @@ Rectangle {
wrapMode: Text.Wrap
ContextMenu.menu: contextMenu
ContextMenu.menu: Qt.platform.os === "ios" ? null : contextMenu
ContextMenuType {
id: contextMenu

View File

@@ -144,7 +144,7 @@ Item {
}
}
ContextMenu.menu: contextMenu
ContextMenu.menu: Qt.platform.os === "ios" ? null : contextMenu
ContextMenuType {
id: contextMenu

View File

@@ -69,7 +69,9 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
NewsModel.markAsRead(index)
if (!isUpdate) {
NewsModel.markAsRead(index)
}
NewsModel.processedIndex = index
PageController.goToPage(PageEnum.PageSettingsNewsDetail)
}

View File

@@ -3,6 +3,8 @@
#include <QObject>
#include <QString>
#include <QRegularExpression>
#include <QSet>
#include "../client/core/utils/utilities.h"
@@ -15,7 +17,8 @@ enum PermittedProcess {
OpenVPN,
Wireguard,
Tun2Socks,
CertUtil
CertUtil,
_Count
};
inline QString permittedProcessPath(PermittedProcess pid)
@@ -57,16 +60,56 @@ inline QStringList sanitizeArguments(PermittedProcess proc, const QStringList &a
QList<Validator> positionalArgs;
switch (proc) {
case OpenVPN: {
static const QSet<QString> blocked = {
QStringLiteral("--script-security"),
QStringLiteral("--up"),
QStringLiteral("--down"),
QStringLiteral("--route-up"),
QStringLiteral("--ipchange"),
QStringLiteral("--tls-verify"),
QStringLiteral("--plugin"),
QStringLiteral("--auth-user-pass-verify"),
QStringLiteral("--learn-address"),
QStringLiteral("--client-connect"),
QStringLiteral("--client-disconnect"),
QStringLiteral("--management"),
QStringLiteral("--management-external-key")
};
QStringList out;
for (int i = 0; i < args.size(); ++i) {
if (blocked.contains(args[i])) {
qWarning() << "IPC: blocked OpenVPN argument:" << args[i];
++i; // skip following value
continue;
}
out << args[i];
}
return out;
}
case Wireguard: {
static const QRegularExpression hookRe(
QStringLiteral(R"((?i)(PostUp|PreUp|PostDown|PreDown)\s*=)"));
QStringList out;
for (const QString& a : args) {
if (hookRe.match(a).hasMatch()) {
qWarning() << "IPC: blocked WireGuard hook argument:" << a;
continue;
}
out << a;
}
return out;
}
case Tun2Socks:
namedArgs["-device"] = [](const QString& v) { return v.startsWith("tun://"); };
namedArgs["-proxy"] = [](const QString& v) { return v.startsWith("socks5://"); };
break;
default:
//FIXME
case CertUtil:
return args;
default:
return {};
}
QStringList sanitized;
for (int i = 0, pos = 0; i < args.size(); i++) {

View File

@@ -22,6 +22,27 @@
#include "tapcontroller_win.h"
#endif
#ifdef Q_OS_LINUX
#include <sys/socket.h>
#include <sys/types.h>
extern uid_t g_allowedUid;
extern bool g_allowedUidSet;
static bool checkPrivPeerCredentials(QLocalSocket *socket) {
struct ucred cred{};
socklen_t len = sizeof(cred);
if (getsockopt(socket->socketDescriptor(), SOL_SOCKET, SO_PEERCRED, &cred, &len) != 0) {
qWarning() << "IpcServer: SO_PEERCRED failed, rejecting privileged process connection";
return false;
}
if (cred.uid == 0) return true;
if (g_allowedUidSet && cred.uid == g_allowedUid) return true;
qWarning() << "IpcServer: rejected privileged process connection from unauthorized UID" << cred.uid;
return false;
}
#endif
IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent)
{
@@ -48,8 +69,16 @@ int IpcServer::createPrivilegedProcess()
// Make sure any connections are handed to QtRO
QObject::connect(pd.localServer.data(), &QLocalServer::newConnection, this, [pd]() {
qDebug() << "IpcServer new connection";
QLocalSocket *conn = pd.localServer->nextPendingConnection();
#ifdef Q_OS_LINUX
if (!checkPrivPeerCredentials(conn)) {
conn->close();
conn->deleteLater();
return;
}
#endif
if (pd.serverNode) {
pd.serverNode->addHostSideConnection(pd.localServer->nextPendingConnection());
pd.serverNode->addHostSideConnection(conn);
pd.serverNode->enableRemoting(pd.ipcProcess.data());
}
});

View File

@@ -77,6 +77,11 @@ void IpcServerProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode)
void IpcServerProcess::setProgram(int programId)
{
if (programId <= static_cast<int>(amnezia::PermittedProcess::Invalid) ||
programId >= static_cast<int>(amnezia::PermittedProcess::_Count)) {
qWarning() << "IPC: invalid programId" << programId << ", ignoring";
return;
}
m_program = static_cast<amnezia::PermittedProcess>(programId);
m_process->setProgram(amnezia::permittedProcessPath(m_program));
m_process->setArguments({});

View File

@@ -3,6 +3,7 @@
#include <QApplication>
#include <QHostAddress>
#include <QRegularExpression>
#include "../client/core/utils/protocolEnum.h"
#include "../client/core/protocols/protocolUtils.h"
@@ -11,6 +12,37 @@
#include "qjsonarray.h"
#include "version.h"
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
static bool isValidIpOrCidr(const QString &value) {
static const QRegularExpression re(
QStringLiteral(R"(^(\d{1,3}\.){3}\d{1,3}(/\d{1,2})?$)"));
if (!re.match(value).hasMatch()) return false;
const QStringList ipParts = value.split(QLatin1Char('/'))[0].split(QLatin1Char('.'));
for (const QString &part : ipParts) {
bool ok;
int octet = part.toInt(&ok);
if (!ok || octet < 0 || octet > 255) return false;
}
if (value.contains(QLatin1Char('/'))) {
bool ok;
int prefix = value.split(QLatin1Char('/'))[1].toInt(&ok);
if (!ok || prefix < 0 || prefix > 32) return false;
}
return true;
}
static QStringList filterIpList(const QStringList &values) {
QStringList safe;
for (const QString &v : values) {
if (isValidIpOrCidr(v))
safe << v;
else
qWarning() << "IPC: rejected invalid IP/CIDR value:" << v;
}
return safe;
}
#endif
#ifdef Q_OS_WIN
#include "../client/platforms/windows/daemon/windowsfirewall.h"
#include "../client/platforms/windows/daemon/windowsdaemon.h"
@@ -166,7 +198,11 @@ bool KillSwitch::disableAllTraffic() {
bool KillSwitch::resetAllowedRange(const QStringList &ranges) {
#ifdef Q_OS_LINUX
m_allowedRanges = filterIpList(ranges);
#else
m_allowedRanges = ranges;
#endif
#ifdef Q_OS_LINUX
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), true);
@@ -189,7 +225,12 @@ bool KillSwitch::resetAllowedRange(const QStringList &ranges) {
}
bool KillSwitch::addAllowedRange(const QStringList &ranges) {
for (const QString &range : ranges) {
#ifdef Q_OS_LINUX
const QStringList safeRanges = filterIpList(ranges);
#else
const QStringList &safeRanges = ranges;
#endif
for (const QString &range : safeRanges) {
if (!range.isEmpty() && !m_allowedRanges.contains(range)) {
m_allowedRanges.append(range);
}
@@ -317,9 +358,9 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets);
LinuxFirewall::updateAllowNets(allownets);
LinuxFirewall::updateAllowNets(filterIpList(allownets));
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), blockAll);
LinuxFirewall::updateBlockNets(blocknets);
LinuxFirewall::updateBlockNets(filterIpList(blocknets));
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("130.allowMarkedXray"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
@@ -328,23 +369,36 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
QStringList dnsServers;
dnsServers.append(configStr.value(amnezia::configKey::dns1).toString());
const QString dns1 = configStr.value(amnezia::configKey::dns1).toString();
if (isValidIpOrCidr(dns1))
dnsServers.append(dns1);
else if (!dns1.isEmpty())
qWarning() << "IPC: rejected invalid dns1:" << dns1;
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (!configStr.value(amnezia::configKey::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) {
dnsServers.append(configStr.value(amnezia::configKey::dns2).toString());
if (!dns1.contains(amnezia::protocols::dns::amneziaDnsIp)) {
const QString dns2 = configStr.value(amnezia::configKey::dns2).toString();
if (isValidIpOrCidr(dns2))
dnsServers.append(dns2);
else if (!dns2.isEmpty())
qWarning() << "IPC: rejected invalid dns2:" << dns2;
}
dnsServers.append("127.0.0.1");
dnsServers.append("127.0.0.53");
for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) {
if (!dns.isString()) {
break;
}
dnsServers.append(dns.toString());
const QString dnsStr = dns.toString();
if (isValidIpOrCidr(dnsStr))
dnsServers.append(dnsStr);
else if (!dnsStr.isEmpty())
qWarning() << "IPC: rejected invalid allowedDnsServer:" << dnsStr;
}
LinuxFirewall::updateDNSServers(dnsServers);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
@@ -360,28 +414,40 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll);
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets);
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, QStringLiteral("allownets"), allownets);
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, QStringLiteral("allownets"), filterIpList(allownets));
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets);
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), blocknets);
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), filterIpList(blocknets));
MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
QStringList dnsServers;
dnsServers.append(configStr.value(amnezia::configKey::dns1).toString());
const QString dns1 = configStr.value(amnezia::configKey::dns1).toString();
if (isValidIpOrCidr(dns1))
dnsServers.append(dns1);
else if (!dns1.isEmpty())
qWarning() << "IPC: rejected invalid dns1:" << dns1;
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (!configStr.value(amnezia::configKey::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) {
dnsServers.append(configStr.value(amnezia::configKey::dns2).toString());
if (!dns1.contains(amnezia::protocols::dns::amneziaDnsIp)) {
const QString dns2 = configStr.value(amnezia::configKey::dns2).toString();
if (isValidIpOrCidr(dns2))
dnsServers.append(dns2);
else if (!dns2.isEmpty())
qWarning() << "IPC: rejected invalid dns2:" << dns2;
}
for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) {
if (!dns.isString()) {
break;
}
dnsServers.append(dns.toString());
const QString dnsStr = dns.toString();
if (isValidIpOrCidr(dnsStr))
dnsServers.append(dnsStr);
else if (!dnsStr.isEmpty())
qWarning() << "IPC: rejected invalid allowedDnsServer:" << dnsStr;
}
MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);

View File

@@ -17,6 +17,35 @@
#include "tapcontroller_win.h"
#endif
#ifdef Q_OS_LINUX
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
uid_t g_allowedUid = static_cast<uid_t>(-1);
bool g_allowedUidSet = false;
static bool checkPeerCredentials(QLocalSocket *socket) {
struct ucred cred{};
socklen_t len = sizeof(cred);
if (getsockopt(socket->socketDescriptor(), SOL_SOCKET, SO_PEERCRED, &cred, &len) != 0) {
qWarning() << "LocalServer: SO_PEERCRED failed, rejecting connection";
return false;
}
if (cred.uid == 0) return true;
if (!g_allowedUidSet) {
g_allowedUid = cred.uid;
g_allowedUidSet = true;
qDebug() << "LocalServer: registered session UID" << g_allowedUid;
}
if (cred.uid != g_allowedUid) {
qWarning() << "LocalServer: rejected connection from unauthorized UID" << cred.uid;
return false;
}
return true;
}
#endif
namespace {
Logger logger("WgDaemonServer");
}
@@ -35,7 +64,15 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
QObject::connect(m_server.data(), &QLocalServer::newConnection, this, [this]() {
qDebug() << "LocalServer new connection";
m_serverNode.addHostSideConnection(m_server->nextPendingConnection());
QLocalSocket *conn = m_server->nextPendingConnection();
#ifdef Q_OS_LINUX
if (!checkPeerCredentials(conn)) {
conn->close();
conn->deleteLater();
return;
}
#endif
m_serverNode.addHostSideConnection(conn);
if (!m_isRemotingEnabled) {
m_isRemotingEnabled = true;