Changelog
[0.9.1] - 2026-06-19
Fixed
- Security (CRITICAL): macOS helper shell-injection RCE —
aivpn_helper.shexecutednohup $CMDwith an unquoted$KEYvariable; a crafted connection key containing shell metacharacters would run arbitrary commands as root via the privileged helper. Script removed entirely; therunClientCommand()Swift code path now applies the same symlink-resolvedALLOWED_CLIENT_PATHSallowlist thatstartClient()uses, so arbitrary binary execution via the helper Unix socket is no longer possible. - Security (CRITICAL): macOS helper
runClientCommandallowlist bypass —runClientCommand()accepted an arbitrarybinaryPathargument without checkingALLOWED_CLIENT_PATHS; any local user with access to the helper socket could execute an arbitrary binary as root. Allowlist check now applied identically tostartClient(). - Security: integer underflow on malformed
pad_len—protocol.rssubtractedpad_lenfrom the packet length before a bounds check, wrapping to a huge value and causing a read beyond the buffer on crafted packets; bounds check added before subtraction. - Security:
KeyRotatekey length not validated — a malformedKeyRotatecontrol packet withnew_eph_pub_len ≠ 32would causefrom_raw_partsto alias memory of the wrong length; explicit!= 32rejection added. - Security:
AckPacketminimum-length guard off by 2 — the guard comparedlen >= 5but then read fields at byte indices 5 and 6, allowing a 5- or 6-byte packet to trigger an out-of-bounds read; corrected tolen >= 7. - Security:
mask.rsout-of-bounds in distribution sampling —LogNormalandGammasamplers indexedparams[]without checking length;Empiricalpassed an empty slice togen_range, causing a panic. Guards added for each distribution variant. - Security:
gateway.rsexpect()panic when no masks loaded — callingexpect()on an empty mask list crashed the server process; replaced with a gracefulwarn!()+ error return. - Security:
neural.rsassert!()panic on short signature vector — a mask with a shortsignature_vectortriggered an assertion failure crashing the process; replaced withwarn!()and fallback behaviour. - Security: Android
fcntl(F_SETFL, O_NONBLOCK)return value ignored — failure to set non-blocking mode was silently accepted; the dup'd fd was also not closed on subsequent failure, potentially blockingAsyncFd. Both issues fixed. - Security: iOS
server_host/server_keynull pointer dereference —aivpn_run_tunnelFFI entry dereferencedserver_hostandserver_keyviaCStr::from_ptrwithout a null check; null pointers now return an error immediately. - iOS:
completionHandlerfired before Rust handshake completed —PacketTunnelProvidercalledcompletionHandler(nil)immediately afterthread.start(), marking the tunnel connected before the Rust handshake finished; wired via aTunnelReadyBoxC trampoline so the callback fires only afteron_readyis invoked from the Rust side. - iOS: DNS routing inverted in full-tunnel mode —
matchDomainswas incorrectly set toexcludedDomains, routing excluded-domain DNS through the VPN and leaking general DNS outside the tunnel in full-tunnel mode; set tonilso all DNS queries are routed through the VPN DNS server. - Android:
foregroundServiceTypemissing for API 34+ —startForeground()was called without the requiredforegroundServiceTypeon Android 14 (API 34+), causing a crash on newer devices;FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTEDadded. - Android:
JSONExceptionon malformed connection keys —MainActivityusedgetString()for PSK and VPN IP JSON fields; absent or null fields threw an uncaughtJSONException; changed tooptString()with defaults. - Android: underlying network not validated —
NetworkCallbackaccepted any available network including captive portals as the VPN transport; now requiresNET_CAPABILITY_VALIDATED. - macOS: full-tunnel route errors silently ignored —
setTunnelNetworkSettingsroute configuration failures were swallowed and the tunnel started with a broken default route; propagated as errors so the connection fails cleanly. - macOS: proxy mode 'binary not found' outside
/Applications—startClient()resolved the helper binary path relative to/Applications/AIVPN.app; running from any other path caused immediate failure; resolution now walks from the helper's own bundle path. - Windows:
disconnect()blocked the egui UI thread —VpnManager::disconnect()contained afor _ in 0..5 { thread::sleep(100ms) }loop beforechild.kill(), stalling the render loop for up to 500 ms on every disconnect; replaced with immediatechild.kill() + child.wait(). - Windows: startup blocked on
get_device_public_key()—AivpnApp::new()calledVpnManager::get_device_public_key()synchronously, blocking the egui event loop until the subprocess returned; moved to a background thread with anmpsc::channel;update()polls viatry_recv()and fills the field lazily. - Windows: bench command shows console window —
run_bench_blocking()spawnedaivpn-client.exewithoutCREATE_NO_WINDOW; a blank console window flashed on every latency test; flag added with#[cfg(windows)]. - Windows: edit-key form loses
exclude_routes—KeyAction::Edithandler restored all key fields exceptexclude_routes; the missingapp.new_key_exclude_routes = key.exclude_routes.join("\n")assignment caused the field to appear empty when editing an existing key. - Kernel module: port Rust bindings to Linux 7.x
Rust-for-LinuxAPI —dev.rsfully rewritten forkernel::miscdevice::{MiscDevice, MiscDeviceOptions, MiscDeviceRegistration}andkernel::uaccess::{UserSlice, UserPtr};#[pin_data]+KBox::pin_init+try_pin_init!used for the pinned device struct;ioctlreturn type changed fromResult<i32>toResult<isize>;module!macroauthor:key changed toauthors: [...];#![feature(allocator_api)]removed (not permitted in kernel modules). - Kernel module:
ktime_get_ms()undefined on Linux 6.4+ — function removed in Linux 6.4; replaced withktime_to_ms(ktime_get())wrapped in a localaivpn_ktime_ms()helper. - Kernel module:
crypto_memneq()undeclared —session_table.cusedcrypto_memneqwithout#include <crypto/algapi.h>; include added. - Kernel module:
aivpn_udp_hook_install_by_fd()unresolved at link —dev.rsdeclared and called the function but it was never implemented in C; added toudp_hook.cusingsockfd_lookup()/sockfd_put(). - MikroTik Docker: native
stripcorrupts aarch64 cross-compiled binary — the builder stage ranstrip /aivpn-clientwith the host x86_64 tool after cross-compiling for aarch64; native strip cannot process foreign-architecture ELF and silently corrupts the binary;stripstep removed. - Build: Makefile targets fail with
rustup: not found—make windows,make ios, andmake kernelinvokedrustupandcargoby name; in environments where the shell profile was not sourced (CI,sudo make) commands resolved to the system package-manager toolchain or failed outright;export PATH := $(HOME)/.cargo/bin:$(PATH)added at the top of the Makefile. - Android build: system
rustcshadows rustup whenJAVA_HOME=/usr—build-rust-android.shprepended${JAVA_HOME}/binto PATH after rustup setup; on systems wherejavaresolves to/usr/bin/javathis placed/usr/bin(which contains the distro-packagedrustc) before~/.cargo/bin, causingcargo ndkto compile with arustcthat has no Android targets;~/.cargo/binis now kept first after theJAVA_HOME/binprepend.
Added
- Server:
network_config.mtu: "auto"—network_config.mtuinserver.jsonnow accepts"auto"(or may be omitted entirely). When set to"auto", the advertised client MTU is derived from the samedetect_mtu()call that setstun_mtu, keeping both values in sync automatically. On constrained links (VXLAN/GRE overlays, Kubernetes pods, PPPoE) where the physical MTU is below 1410 bytes,"auto"prevents the previous mismatch where clients were told to use 1346-byte inner packets while the server TUN could only forward 1236-byte packets, causing packet loss. The invariantnetwork_config.mtu ≤ tun_mtuis now enforced at startup (oversized values are clamped with a warning).config/server.json.exampleupdated to"mtu": "auto". - Kernel module:
aivpn_udp_hook_install_by_fd()ioctl — new C function inudp_hook.callows userspace to install the UDP RX hook by passing a socket file descriptor viaIOC_SET_UDP_SOCK, eliminating any need for out-of-band socket passing. - CI: aarch64 musl server + client in release matrix —
aivpn-server-linux-aarch64-muslandaivpn-client-linux-aarch64-muslstatic binaries now built and published on every tagged release.
Changed
- Build system: unified
Makefilereplacesscripts/— all per-platform build scripts consolidated into a single top-levelMakefilewith named targets:make server,make client,make windows,make ios,make macos,make android,make kernel,make mikrotik,make openwrt. CI workflows updated accordingly. - Version bumped 0.9.0 → 0.9.1 across workspace
Cargo.toml, all crateCargo.tomlfiles, macOSInfo.plist(build 8), iOSApp/Info.plistandTunnel/Info.plist(build 6).
[0.9.1] — 2026-06-19
Исправлено
- Безопасность (КРИТИЧЕСКОЕ): RCE через shell-injection в helper macOS —
aivpn_helper.shвыполнялnohup $CMDс неэкранированной переменной$KEY; сформированный connection key со спецсимволами оболочки позволял выполнить произвольный код от имени root через привилегированный helper. Скрипт удалён полностью; функцияrunClientCommand()в Swift-коде теперь применяет тот же allowlistALLOWED_CLIENT_PATHSс разрешением симлинков, что иstartClient(). - Безопасность (КРИТИЧЕСКОЕ): обход allowlist в
runClientCommandhelper'а macOS —runClientCommand()принимал произвольныйbinaryPathбез проверкиALLOWED_CLIENT_PATHS; любой локальный пользователь с доступом к сокету helper'а мог выполнить произвольный бинарник от имени root. Проверка allowlist теперь идентичнаstartClient(). - Безопасность: целочисленное переполнение при некорректном
pad_len—protocol.rsвычиталpad_lenдо проверки границ; результат оборачивался в огромное число и вызывал чтение за пределами буфера; проверка добавлена до вычитания. - Безопасность: длина ключа
KeyRotateне проверялась — сформированный пакетKeyRotateсnew_eph_pub_len ≠ 32вызывалfrom_raw_partsс неверной длиной; добавлена явная проверка с отклонением пакета. - Безопасность: граница минимальной длины
AckPacketзанижена на 2 — проверкаlen >= 5допускала out-of-bounds-чтение по индексам 5 и 6; исправлено наlen >= 7. - Безопасность: выход за границы в сэмплировании
mask.rs—LogNormalиGammaобращались кparams[]без проверки длины;Empiricalвызывалgen_rangeс пустым срезом, вызывая панику; добавлены проверки для каждого варианта. - Безопасность:
expect()вgateway.rsпри отсутствии масок — крашил серверный процесс; заменён наwarn!()и возврат ошибки. - Безопасность:
assert!()вneural.rsпри короткомsignature_vector— крашил процесс; заменено наwarn!()с fallback-поведением. - Безопасность: возвращаемое значение
fcntlигнорировалось на Android — ошибка установки O_NONBLOCK игнорировалась; дублированный fd не закрывался при ошибках, блокируяAsyncFd; оба дефекта исправлены. - Безопасность: разыменование null-указателей
server_host/server_keyна iOS — FFI-точка входаaivpn_run_tunnelразыменовывала указатели черезCStr::from_ptrбез проверки на null; нулевые указатели теперь немедленно возвращают ошибку. - iOS:
completionHandlerвызывался до завершения рукопожатия Rust — ОС помечала туннель подключённым до завершения Rust-рукопожатия; теперь используется трамплинTunnelReadyBox— callback вызывается только послеon_readyиз Rust. - iOS: DNS-маршрутизация инвертирована в full-tunnel режиме —
matchDomainsошибочно устанавливался вexcludedDomains, пропуская общий DNS мимо туннеля; исправлено наnil— весь DNS маршрутизируется через VPN. - Android: отсутствует
foregroundServiceTypeдля API 34+ —startForeground()без обязательного типа крашил приложение на Android 14; добавленоFOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED. - Android:
JSONExceptionпри некорректных ключах подключения —getString()для PSK и VPN IP выбрасывал непойманное исключение; заменено наoptString()с дефолтными значениями. - Android: невалидированная сеть принималась как транспорт VPN —
NetworkCallbackпринимал captive portal'ы и невалидированные сети; теперь требуетсяNET_CAPABILITY_VALIDATED. - macOS: ошибки настройки маршрутов full-tunnel молча игнорировались — туннель запускался с нерабочим default route; теперь ошибки пробрасываются и соединение завершается корректно.
- macOS: proxy-режим не находил бинарник вне
/Applications— путь разрешался относительно/Applications/AIVPN.app; исправлено — путь определяется от bundle самого helper'а. - Windows:
disconnect()блокировал UI-поток egui — цикл5 × sleep(100мс)передchild.kill()замораживал render loop на до 500 мс; заменено наchild.kill() + child.wait()без задержки. - Windows: запуск блокировался на
get_device_public_key()— синхронный вызов подпроцесса вnew()блокировал egui event loop; перенесено в фоновый поток сmpsc::channel; поле заполняется лениво черезtry_recv()вupdate(). - Windows: bench-команда показывала мигающую консоль —
aivpn-client.exeзапускался безCREATE_NO_WINDOW; флаг добавлен через#[cfg(windows)]. - Windows: форма редактирования ключа теряла
exclude_routes— пропущенное присвоениеapp.new_key_exclude_routes = key.exclude_routes.join("\n")в обработчикеKeyAction::Edit; поле теперь восстанавливается корректно. - Модуль ядра: перенос Rust-привязок на Linux 7.x
Rust-for-LinuxAPI —dev.rsполностью переписан:kernel::miscdevice::{MiscDevice, MiscDeviceOptions, MiscDeviceRegistration},kernel::uaccess::{UserSlice, UserPtr},#[pin_data]+KBox::pin_init+try_pin_init!; тип возвратаioctl→Result<isize>;author:→authors: [...];#![feature(allocator_api)]удалён. - Модуль ядра:
ktime_get_ms()удалён в Linux 6.4 — заменён наktime_to_ms(ktime_get())в хелпереaivpn_ktime_ms(). - Модуль ядра:
crypto_memneq()не объявлен — добавлен#include <crypto/algapi.h>вsession_table.c. - Модуль ядра:
aivpn_udp_hook_install_by_fd()не разрешался при линковке — реализация добавлена вudp_hook.cчерезsockfd_lookup()/sockfd_put(). - MikroTik Docker: нативный
stripповреждал aarch64 ELF — хостовый x86_64stripне обрабатывает ELF чужой архитектуры; шаг удалён из builder-стадии. - Сборка: цели Makefile завершались с
rustup: not found— в средах без sourced-профиля команды разрешались в системный toolchain; добавленexport PATH := $(HOME)/.cargo/bin:$(PATH)в начало Makefile. - Android build: системный
rustcперекрывал rustup приJAVA_HOME=/usr—${JAVA_HOME}/bin(=/usr/bin) ставился раньше~/.cargo/binв PATH, подставляя системныйrustcбез Android-таргетов;~/.cargo/binтеперь принудительно первым.
Добавлено
- Сервер:
network_config.mtu: "auto"— полеnetwork_config.mtuвserver.jsonтеперь принимает значение"auto"(или может быть опущено). При"auto"рекламируемый клиентам MTU берётся из того же вызоваdetect_mtu(), что устанавливаетtun_mtu, — оба значения всегда синхронизированы. На ограниченных линках (VXLAN/GRE-оверлеи, поды Kubernetes, PPPoE), где физический MTU ниже 1410 байт,"auto"устраняет рассинхронизацию, при которой клиентам сообщался MTU 1346 байт, тогда как серверный TUN мог форвардировать лишь 1236-байтные пакеты. Инвариантnetwork_config.mtu ≤ tun_mtuтеперь принудительно соблюдается при запуске: завышенные значения усекаются с предупреждением.config/server.json.exampleобновлён на"mtu": "auto". - Модуль ядра: ioctl
aivpn_udp_hook_install_by_fd()— новая C-функция вudp_hook.cпозволяет userspace устанавливать UDP-хук передачей fd черезIOC_SET_UDP_SOCK. - CI: aarch64 musl в release-матрице — статические бинарники
aivpn-server-linux-aarch64-muslиaivpn-client-linux-aarch64-muslпубликуются при каждом теге релиза.
Изменено
- Система сборки: единый
Makefileзаменяетscripts/— все скрипты сборки по платформам заменены именованными целями:make server,make client,make windows,make ios,make macos,make android,make kernel,make mikrotik,make openwrt. - Версия 0.9.0 → 0.9.1 обновлена в
Cargo.tomlворкспейса, во всехCargo.tomlкрейтов, macOSInfo.plist(сборка 8), iOSApp/Info.plistиTunnel/Info.plist(сборка 6).