Compare commits

...

13 Commits
v0.3.2 ... main

Author SHA1 Message Date
pixl
628ab937a2
Merge pull request #476 from PixlOne/fix-cve-2024-45752
Fix CVE-2024-45752
2024-09-27 20:46:28 -04:00
pixl
9495516e0c
Fix CVE-2024-45752
Prevents arbitrary users from accessing d-bus interface. Fixes #473.
This change now requires any application using the LogiOps D-Bus
interface to run as root.
2024-09-27 20:43:01 -04:00
pixl
237fa5fbd3
Merge pull request #414 from wprzytula/fix-uint8_t-cid
fix bug: represent cid as `uint16_t`, not `uint8_t`
2024-05-27 19:55:13 -07:00
pixl
456efb4db0
Merge pull request #441 from pasanflo/patch-1
Update TESTED.md
2024-05-27 19:54:49 -07:00
pixl
d79d050bf4
Merge pull request #415 from kostadinsh/gcc-14
Add include <algorithm> to fix building with gcc 14
2024-05-27 19:50:21 -07:00
Pablo Sánchez Flores
e9f8072a0c
Update TESTED.md
I've tested my Logitech MX Master 3S with LogiOps, and I can see that it's working with the config name as shown on the PR.
2024-03-04 13:17:30 +01:00
Kostadin Shishmanov
da742af3a5
Add include <algorithm> to fix building with gcc 14
With gcc 14 some C++ Standard Library headers have been changed to no longer include other headers that were being used internally by the library. In logiops's case it is the <algorithm> header.

Downstream Gentoo bug:
https://bugs.gentoo.org/917002

GCC 14 porting guide:
https://gcc.gnu.org/gcc-14/porting_to.html

Signed-off-by: Kostadin Shishmanov <kocelfc@tutanota.com>
2023-11-07 19:10:29 +02:00
Wojciech Przytuła
700070c651 fix bug: represent cid as uint16_t
The function ReprogControlsV4::setControlReporting() erroneously took
cid as uint8_t. Because of that, reports contained only lower byte
of any cid, so any ReprogControls request for such cid resulted
in error response. Lack of error response handling in this library
led to timeout.

E.g.:
```
[DEBUG] Configuring button: 0x103
[RAWREPORT] /dev/hidraw1 OUT: 11 ff 08 32 00 03 22 01 03 00 00 00 00 00 00 00 00 00 00 00
[RAWREPORT] /dev/hidraw1 IN:  11 ff ff 08 32 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00
```
We can see only lower byte (03) of `cid` present. Thus we get an error
response (with feature id `ff`), but the logiops keeps waiting for
response with feature id `08` and hence eventually timeouts.
2023-11-07 16:22:50 +01:00
pixl
94f6dbab53
Merge pull request #399 from PixlOne/cmake-fixes
CMake fixes: NDEBUG in None build type and allow shared ipcgull library
2023-07-12 17:39:21 -04:00
pixl
f2680bdf5f
Use NDEBUG for None build type 2023-07-12 14:08:59 -04:00
pixl
d51fd5c344
Update ipcgull to allow for shared library options 2023-07-12 14:03:08 -04:00
pixl
31d1955faf
Merge pull request #390 from PixlOne/fix-iomon-locks
Resolve deadlocking when adding to IOMonitor
2023-07-12 13:59:18 -04:00
pixl
cb7a2dad7c
Resolve deadlocking when adding to IOMonitor
Do not lock run_mutex while running an I/O handler.
2023-05-20 20:55:01 -04:00
14 changed files with 140 additions and 127 deletions

View File

@ -6,6 +6,7 @@ project(logiops)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
set(CMAKE_CXX_FLAGS_NONE "${CMAKE_CXX_FLAGS_NONE} -DNDEBUG")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -Wall -Wextra")
set(CMAKE_CXX_STANDARD 20)

View File

@ -5,6 +5,7 @@ This is not by any means an exhaustive list. Many more devices are supported but
| Device | Compatible? | Config Name |
| :-----------------: | :---------: | :------------------------------------: |
| MX Master 3S | Yes | `MX Master 3S` |
| MX Master 3 | Yes | `Wireless Mouse MX Master 3` |
| MX Master 3 for Mac | Yes | `MX Master 3 for Mac` |
| MX Master 2S | Yes | `Wireless Mouse MX Master 2S` |

@ -1 +1 @@
Subproject commit 4f22a43e3380dc1e9c0a490201f4d745390b623f
Subproject commit cd0f9a8cefb5b2545e163fceb249fdbcbaf666aa

View File

@ -66,11 +66,11 @@ void DeviceManager::addDevice(std::string path) {
// Check if device is ignored before continuing
{
raw::RawDevice raw_dev(path, self<DeviceManager>().lock());
auto raw_dev = raw::RawDevice::make(path, self<DeviceManager>().lock());
if (config()->ignore.has_value() &&
config()->ignore.value().contains(raw_dev.productId())) {
config()->ignore.value().contains(raw_dev->productId())) {
logPrintf(DEBUG, "%s: Device 0x%04x ignored.",
path.c_str(), raw_dev.productId());
path.c_str(), raw_dev->productId());
return;
}
}

View File

@ -53,7 +53,7 @@ Device::Device(const std::string& path, DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(std::make_shared<raw::RawDevice>(path, monitor)),
_raw_device(raw::RawDevice::make(path, monitor)),
_receiver(nullptr), _path(path), _index(index) {
}

View File

@ -129,7 +129,7 @@ ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) {
return report;
}
void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info) {
void ReprogControls::setControlReporting(uint16_t cid, ControlInfo info) {
// This function does not exist pre-v4 and cannot be emulated, ignore.
(void) cid;
(void) info; // Suppress unused warnings
@ -173,7 +173,7 @@ ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid)
return info;
}
void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info) {
void ReprogControlsV4::setControlReporting(uint16_t cid, ControlInfo info) {
std::vector<uint8_t> params(5);
params[0] = (cid >> 8) & 0xff;
params[1] = cid & 0xff;

View File

@ -100,7 +100,7 @@ namespace logid::backend::hidpp20 {
[[nodiscard]] virtual ControlInfo getControlReporting(uint16_t cid);
// Only controlId (for remap) and flags will be read
virtual void setControlReporting(uint8_t cid, ControlInfo info);
virtual void setControlReporting(uint16_t cid, ControlInfo info);
[[nodiscard]] static std::set<uint16_t> divertedButtonEvent(const hidpp::Report& report);
@ -162,7 +162,7 @@ namespace logid::backend::hidpp20 {
[[nodiscard]] ControlInfo getControlReporting(uint16_t cid) override;
void setControlReporting(uint8_t cid, ControlInfo info) override;
void setControlReporting(uint16_t cid, ControlInfo info) override;
explicit ReprogControlsV4(Device* dev);

View File

@ -89,23 +89,25 @@ void DeviceMonitor::ready() {
_ready = true;
_io_monitor->add(_fd, {
[this]() {
struct udev_device* device = udev_monitor_receive_device(_udev_monitor);
std::string action = udev_device_get_action(device);
std::string dev_node = udev_device_get_devnode(device);
[self_weak = _self]() {
if (auto self = self_weak.lock()) {
struct udev_device* device = udev_monitor_receive_device(self->_udev_monitor);
std::string action = udev_device_get_action(device);
std::string dev_node = udev_device_get_devnode(device);
if (action == "add")
run_task([self_weak = _self, dev_node]() {
if (auto self = self_weak.lock())
self->_addHandler(dev_node);
});
else if (action == "remove")
run_task([self_weak = _self, dev_node]() {
if (auto self = self_weak.lock())
self->_removeHandler(dev_node);
});
if (action == "add")
run_task([self_weak, dev_node]() {
if (auto self = self_weak.lock())
self->_addHandler(dev_node);
});
else if (action == "remove")
run_task([self_weak, dev_node]() {
if (auto self = self_weak.lock())
self->_removeHandler(dev_node);
});
udev_device_unref(device);
udev_device_unref(device);
}
},
[]() {
throw std::runtime_error("udev hangup");

View File

@ -16,7 +16,7 @@
*
*/
#include <backend/raw/IOMonitor.h>
#include <cassert>
#include <util/log.h>
#include <optional>
extern "C"
@ -36,55 +36,6 @@ IOHandler::IOHandler(std::function<void()> r,
error(std::move(err)) {
}
class IOMonitor::io_lock {
std::optional<std::unique_lock<std::mutex>> _lock;
IOMonitor* _io_monitor;
const uint64_t counter = 1;
public:
explicit io_lock(IOMonitor* io_monitor) : _io_monitor(io_monitor) {
_io_monitor->_interrupting = true;
[[maybe_unused]] ssize_t ret = ::write(_io_monitor->_event_fd, &counter, sizeof(counter));
assert(ret == sizeof(counter));
_lock.emplace(_io_monitor->_run_mutex);
}
io_lock(const io_lock&) = delete;
io_lock& operator=(const io_lock&) = delete;
io_lock(io_lock&& o) noexcept: _lock(std::move(o._lock)), _io_monitor(o._io_monitor) {
o._lock.reset();
o._io_monitor = nullptr;
}
io_lock& operator=(io_lock&& o) noexcept {
if (this != &o) {
_lock = std::move(o._lock);
_io_monitor = o._io_monitor;
o._lock.reset();
o._io_monitor = nullptr;
}
return *this;
}
~io_lock() noexcept {
if (_lock && _io_monitor) {
uint64_t buf{};
[[maybe_unused]] const ssize_t ret = ::read(
_io_monitor->_event_fd, &buf, sizeof(counter));
assert(ret != -1);
if (buf == counter) {
_io_monitor->_interrupting = false;
_io_monitor->_interrupt_cv.notify_one();
}
}
}
};
IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)),
_event_fd(eventfd(0, EFD_NONBLOCK)) {
if (_epoll_fd < 0) {
@ -106,12 +57,7 @@ IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)),
throw std::system_error(errno, std::generic_category());
}
_fds.emplace(std::piecewise_construct, std::forward_as_tuple(_event_fd),
std::forward_as_tuple([]() {}, []() {
throw std::runtime_error("event_fd hangup");
}, []() {
throw std::runtime_error("event_fd error");
}));
_fds.emplace(_event_fd, nullptr);
_io_thread = std::make_unique<std::thread>([this]() {
_listen();
@ -122,70 +68,100 @@ IOMonitor::~IOMonitor() noexcept {
_stop();
if (_event_fd >= 0)
close(_event_fd);
::close(_event_fd);
if (_epoll_fd >= 0)
close(_epoll_fd);
::close(_epoll_fd);
}
void IOMonitor::_listen() {
std::unique_lock lock(_run_mutex);
std::vector<struct epoll_event> events;
if (_is_running)
throw std::runtime_error("IOMonitor double run");
_is_running = true;
while (_is_running) {
if (_interrupting) {
_interrupt_cv.wait(lock, [this]() {
return !_interrupting;
});
if (!_is_running)
break;
}
if (events.size() != _fds.size())
events.resize(_fds.size());
int ev_count = ::epoll_wait(_epoll_fd, events.data(), (int) events.size(), -1);
for (int i = 0; i < ev_count; ++i) {
const auto& handler = _fds.at(events[i].data.fd);
if (events[i].events & EPOLLIN)
handler.read();
if (events[i].events & EPOLLHUP)
handler.hangup();
if (events[i].events & EPOLLERR)
handler.error();
std::shared_ptr<IOHandler> handler;
if (events[i].data.fd == _event_fd) {
if (events[i].events & EPOLLIN) {
lock.unlock();
/* Wait until done yielding */
const std::lock_guard yield_lock(_yield_mutex);
uint64_t event;
while (-1 != ::eventfd_read(_event_fd, &event)) { }
lock.lock();
}
} else {
try {
handler = _fds.at(events[i].data.fd);
} catch (std::out_of_range& e) {
continue;
}
lock.unlock();
try {
if (events[i].events & EPOLLIN)
handler->read();
if (events[i].events & EPOLLHUP)
handler->hangup();
if (events[i].events & EPOLLERR)
handler->error();
} catch (std::exception& e) {
logPrintf(ERROR, "Unhandled I/O handler error: %s", e.what());
}
lock.lock();
}
}
}
}
void IOMonitor::_stop() noexcept {
{
[[maybe_unused]] const io_lock lock(this);
_is_running = false;
}
_is_running = false;
_yield();
_io_thread->join();
}
std::unique_lock<std::mutex> IOMonitor::_yield() noexcept {
/* Prevent listener thread from grabbing lock during yielding */
std::unique_lock yield_lock(_yield_mutex);
std::unique_lock run_lock(_run_mutex, std::try_to_lock);
if (!run_lock.owns_lock()) {
::eventfd_write(_event_fd, 1);
run_lock = std::unique_lock<std::mutex>(_run_mutex);
}
return run_lock;
}
void IOMonitor::add(int fd, IOHandler handler) {
[[maybe_unused]] const io_lock lock(this);
const auto lock = _yield();
struct epoll_event event{};
event.events = EPOLLIN | EPOLLHUP | EPOLLERR;
event.data.fd = fd;
// TODO: EPOLL_CTL_MOD
if (_fds.contains(fd))
if (!_fds.contains(fd)) {
if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event))
throw std::system_error(errno, std::generic_category());
_fds.emplace(fd, std::make_shared<IOHandler>(std::move(handler)));
} else {
throw std::runtime_error("duplicate io fd");
if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event))
throw std::system_error(errno, std::generic_category());
_fds.emplace(fd, std::move(handler));
}
}
void IOMonitor::remove(int fd) noexcept {
[[maybe_unused]] const io_lock lock(this);
const auto lock = _yield();
::epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
_fds.erase(fd);
}

View File

@ -21,9 +21,10 @@
#include <atomic>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <thread>
namespace logid::backend::raw {
struct IOHandler {
@ -53,24 +54,21 @@ namespace logid::backend::raw {
void add(int fd, IOHandler handler);
void remove(int fd) noexcept;
private:
void _listen(); // This is a blocking call
void _stop() noexcept;
std::unique_lock<std::mutex> _yield() noexcept;
std::unique_ptr<std::thread> _io_thread;
std::map<int, IOHandler> _fds;
mutable std::mutex _run_mutex;
std::atomic_bool _is_running;
std::mutex _run_mutex;
std::mutex _yield_mutex;
std::atomic_bool _interrupting;
std::condition_variable _interrupt_cv;
std::map<int, std::shared_ptr<IOHandler>> _fds;
std::atomic_bool _is_running;
const int _epoll_fd;
const int _event_fd;
class io_lock;
};
}

View File

@ -117,11 +117,22 @@ RawDevice::RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& mon
auto phys = get_phys(_fd);
_sub_device = std::regex_match(phys, virtual_path_regex);
}
}
void RawDevice::_ready() {
_io_monitor->add(_fd, {
[this]() { _readReports(); },
[this]() { _valid = false; },
[this]() { _valid = false; }
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_readReports();
},
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_valid = false;
},
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_valid = false;
}
});
}

View File

@ -34,7 +34,16 @@ namespace logid::backend::raw {
class IOMonitor;
template <typename T>
class RawDeviceWrapper : public T {
public:
template <typename... Args>
RawDeviceWrapper(Args... args) : T(std::forward<Args>(args)...) { }
};
class RawDevice {
template <typename>
friend class RawDeviceWrapper;
public:
static constexpr int max_data_length = 32;
typedef RawEventHandler EventHandler;
@ -51,7 +60,14 @@ namespace logid::backend::raw {
BusType bus_type;
};
RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor);
template <typename... Args>
static std::shared_ptr<RawDevice> make(Args... args) {
auto raw_dev = std::make_shared<RawDeviceWrapper<RawDevice>>(
std::forward<Args>(args)...);
raw_dev->_self = raw_dev;
raw_dev->_ready();
return raw_dev;
}
~RawDevice() noexcept;
@ -79,6 +95,10 @@ namespace logid::backend::raw {
[[nodiscard]] EventHandlerLock<RawDevice> addEventHandler(RawEventHandler handler);
private:
RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor);
void _ready();
void _readReports();
std::atomic_bool _valid;
@ -91,6 +111,8 @@ namespace logid::backend::raw {
std::shared_ptr<IOMonitor> _io_monitor;
std::weak_ptr<RawDevice> _self;
bool _sub_device = false;
std::shared_ptr<EventHandlerList<RawDevice>> _event_handlers;

View File

@ -22,6 +22,7 @@
#include <type_traits>
#include <functional>
#include <utility>
#include <algorithm>
namespace logid::config {
template<typename T>

View File

@ -3,11 +3,12 @@
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="pizza.pixl.LogiOps"/>
<policy context="default">
<deny receive_sender="pizza.pixl.LogiOps"/>
</policy>
<policy context="default">
<policy user="root">
<allow own="pizza.pixl.LogiOps"/>
<allow send_destination="pizza.pixl.LogiOps"/>
<allow receive_sender="pizza.pixl.LogiOps"/>
</policy>