Skip to content

Commit

Permalink
Use QubesDB for knowing if mic is allowed or not
Browse files Browse the repository at this point in the history
  • Loading branch information
fepitre committed Jul 17, 2024
1 parent cac038a commit 844ce83
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 122 deletions.
4 changes: 1 addition & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ shmoverride/shmoverride.so:

shmoverride/X-wrapper-qubes:
(cd shmoverride; $(MAKE) X-wrapper-qubes)

pulse/pacat-simple-vchan:
$(MAKE) -C pulse pacat-simple-vchan

Expand All @@ -65,8 +65,6 @@ install:
install -D gui-daemon/qubes-guid $(DESTDIR)/usr/bin/qubes-guid
install -m 0644 -D gui-daemon/qubes-guid.1 $(DESTDIR)$(MANDIR)/man1/qubes-guid.1
install -D pulse/pacat-simple-vchan $(DESTDIR)/usr/bin/pacat-simple-vchan
install -D pulse/qubes.AudioInputEnable $(DESTDIR)/etc/qubes-rpc/qubes.AudioInputEnable
install -D pulse/qubes.AudioInputDisable $(DESTDIR)/etc/qubes-rpc/qubes.AudioInputDisable
install -D shmoverride/X-wrapper-qubes $(DESTDIR)/usr/bin/X-wrapper-qubes
install -D shmoverride/shmoverride.so $(DESTDIR)$(LIBDIR)/qubes-gui-daemon/shmoverride.so
install -D -m 0644 gui-daemon/guid.conf $(DESTDIR)/etc/qubes/guid.conf
Expand Down
2 changes: 0 additions & 2 deletions debian/qubes-audio-daemon.install
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
usr/bin/pacat-simple-vchan
etc/qubes-rpc/qubes.AudioInputEnable
etc/qubes-rpc/qubes.AudioInputDisable
149 changes: 62 additions & 87 deletions pulse/pacat-simple-vchan.c
Original file line number Diff line number Diff line change
Expand Up @@ -809,101 +809,95 @@ static void check_vchan_eof_timer(pa_mainloop_api*a, pa_time_event* e,
a->time_restart(e, &restart_tv);
}

int is_rec_allowed_from_qdb(struct userdata *u) {
int new_rec_allowed;
char *qdb_entry = qdb_read(u->qdb, u->qdb_path, NULL);

if (qdb_entry != NULL) {
if (strcmp(qdb_entry, "0") == 0) {
new_rec_allowed = 0;
} else if (strcmp(qdb_entry, "1") == 0) {
new_rec_allowed = 1;
} else {
pacat_log("invalid value from Qubes DB");
new_rec_allowed = -1;
}
} else {
new_rec_allowed = -errno;
if (new_rec_allowed == -ENOENT)
pacat_log("no %s entry in QubesDB", u->qdb_path);
else
pacat_log("unable to obtain %s entry from QubesDB", u->qdb_path);
}

free(qdb_entry);

return new_rec_allowed;
}

static void control_socket_callback(pa_mainloop_api *UNUSED(a),
pa_io_event *UNUSED(e), int fd, pa_io_event_flags_t f,
pa_io_event *UNUSED(e), int UNUSED(fd), pa_io_event_flags_t f,
void *userdata) {

struct userdata *u = userdata;
int client_fd;
char command_buffer[32];
size_t command_len = 0;
int ret;
int new_rec_allowed = -1;

if (!(f & PA_IO_EVENT_INPUT))
return;
pacat_log("callback %d", new_rec_allowed);

client_fd = accept(fd, NULL, NULL);
if (client_fd < 0) {
pacat_log("Accept control connection failed: %s", strerror(errno));
if (!(f & PA_IO_EVENT_INPUT))
return;
}

/* read until either:
* - end of command (\n) is found
* - EOF
*/
do {
ret = read(client_fd, command_buffer+command_len, sizeof(command_buffer)-command_len);
if (ret < 0) {
pacat_log("Control client read failed: %s", strerror(errno));
return;
}
command_len += ret;
if (ret == 0)
break;
} while (!memchr(command_buffer + (command_len-ret), '\n', ret));

if (strncmp(command_buffer, "audio-input 0\n", command_len) == 0) {
new_rec_allowed = 0;
} else if (strncmp(command_buffer, "audio-input 1\n", command_len) == 0) {
new_rec_allowed = 1;
} else {
pacat_log("Invalid command buffer");
return;
}
if (new_rec_allowed != -1) {
new_rec_allowed = is_rec_allowed_from_qdb(u);
if (new_rec_allowed >= 0) {
g_mutex_lock(&u->prop_mutex);
u->rec_allowed = new_rec_allowed;
pacat_log("Setting audio-input to %s", u->rec_allowed ? "enabled" : "disabled");
if (u->rec_allowed && u->rec_requested) {
pacat_log("Recording start");
pa_stream_cork(u->rec_stream, 0, NULL, NULL);
} else if (!u->rec_allowed && u->rec_stream &&
(u->rec_requested || !pa_stream_is_corked(u->rec_stream))) {
pacat_log("Recording stop");
pa_stream_cork(u->rec_stream, 1, NULL, NULL);
if (new_rec_allowed != u->rec_allowed) {
u->rec_allowed = new_rec_allowed;
pacat_log("Setting audio-input to %s", u->rec_allowed ? "enabled" : "disabled");
if (u->rec_allowed && u->rec_requested) {
pacat_log("Recording start");
pa_stream_cork(u->rec_stream, 0, NULL, NULL);
} else if (!u->rec_allowed && u->rec_stream &&
(u->rec_requested || !pa_stream_is_corked(u->rec_stream))) {
pacat_log("Recording stop");
pa_stream_cork(u->rec_stream, 1, NULL, NULL);
}
if (!qdb_write(u->qdb, u->qdb_path, new_rec_allowed ? "1" : "0", 1)) {
pacat_log("Failed to write QubesDB %s: %s", u->qdb_path, strerror(errno));
}
}
g_mutex_unlock(&u->prop_mutex);
if (!qdb_write(u->qdb, u->qdb_path, new_rec_allowed ? "1" : "0", 1)) {
pacat_log("Failed to write QubesDB %s: %s", u->qdb_path, strerror(errno));
}
}
/* accept only one command per connection */
close(client_fd);
}

static int setup_control(struct userdata *u) {
int socket_fd = -1;
/* better safe than sorry - zero initialize the buffer */
struct sockaddr_un addr = { 0 };
int rec_allowed;

socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socket_fd == -1) {
pacat_log("socket failed: %s", strerror(errno));
u->qdb = qdb_open(NULL);
if (!u->qdb) {
pacat_log("qdb_open failed: %s", strerror(errno));
goto fail;
}

if ((size_t)snprintf(addr.sun_path, sizeof(addr.sun_path),
"/var/run/qubes/audio-control.%s", u->name)
>= sizeof(addr.sun_path)) {
pacat_log("VM name too long");
if (asprintf(&u->qdb_path, "/audio-input/%s", u->name) < 0) {
pacat_log("QubesDB path setup failed: %s", strerror(errno));
u->qdb_path = NULL;
goto fail;
}
/* without this line, the bind() fails in many linux versions
with Invalid Argument, and mic cannot attach */
addr.sun_family = AF_UNIX;

/* ignore result */
unlink(addr.sun_path);

if (bind(socket_fd, &addr, sizeof(addr)) == -1) {
pacat_log("bind to %s failed: %s", addr.sun_path, strerror(errno));
if (!qdb_watch(u->qdb, u->qdb_path)) {
pacat_log("failed to setup watch on %s: %m\n", u->qdb_path);
goto fail;
}

if (listen(socket_fd, 5) == -1) {
pacat_log("listen on %s failed: %s", addr.sun_path, strerror(errno));
socket_fd = qdb_watch_fd(u->qdb);
if (socket_fd < 0)
goto fail;

rec_allowed = is_rec_allowed_from_qdb(u);
if (rec_allowed >= 0) {
pacat_log("mic allowed: initial value read from Qubes DB '%d'", rec_allowed);
u->rec_allowed = rec_allowed;
}

u->control_socket_event = u->mainloop_api->io_new(u->mainloop_api,
Expand All @@ -913,23 +907,6 @@ static int setup_control(struct userdata *u) {
goto fail;
}

u->qdb = qdb_open(NULL);
if (!u->qdb) {
pacat_log("qdb_open failed: %s", strerror(errno));
goto fail;
}

if (asprintf(&u->qdb_path, "/audio-input/%s", u->name) < 0) {
pacat_log("QubesDB path setup failed: %s", strerror(errno));
u->qdb_path = NULL;
goto fail;
}

if (!qdb_write(u->qdb, u->qdb_path, "0", 1)) {
pacat_log("qdb_write failed: %s", strerror(errno));
goto fail;
}

u->control_socket_fd = socket_fd;

return 0;
Expand All @@ -956,8 +933,6 @@ static void control_cleanup(struct userdata *u) {
u->mainloop_api->io_free(u->control_socket_event);
if (u->control_socket_fd > 0)
close(u->control_socket_fd);
if (u->qdb && u->qdb_path)
qdb_rm(u->qdb, u->qdb_path);
if (u->qdb_path)
free(u->qdb_path);
if (u->qdb)
Expand Down
4 changes: 0 additions & 4 deletions pulse/qubes.AudioInputDisable

This file was deleted.

4 changes: 0 additions & 4 deletions pulse/qubes.AudioInputEnable

This file was deleted.

87 changes: 67 additions & 20 deletions qubesguidaemon/mic.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class MicDeviceExtension(qubes.ext.Extension):
def __init__(self):
super(MicDeviceExtension, self).__init__()

def get_device(self, app):
@staticmethod
def get_device(app):
return MicDevice(
app.domains[0], product="microphone", manufacturer="build-in"
)
Expand Down Expand Up @@ -99,7 +100,7 @@ def on_device_list_attached_mic(self, vm, event, persistent=None):
)
if untrusted_audio_input == b"1":
# (device, options)
yield (self.get_device(vm.app), {})
yield self.get_device(vm.app), {}

@qubes.ext.handler("device-pre-attach:mic")
async def on_device_pre_attach_mic(self, vm, event, device, options):
Expand All @@ -123,16 +124,22 @@ async def on_device_pre_attach_mic(self, vm, event, device, options):
raise qubes.exc.QubesVMNotRunningError(
audiovm, "Audio VM {} isn't running".format(audiovm)
)
try:
await audiovm.run_service_for_stdio(
"qubes.AudioInputEnable+{}".format(vm.name)
)
except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(
vm,
"Failed to attach audio input from {!s} to {!s}: "
"pulseaudio agent not running".format(audiovm, vm),
)

if audiovm.features.check_with_netvm(
"supported-rpc.qubes.AudioInputEnable", False
):
try:
await audiovm.run_service_for_stdio(
"qubes.AudioInputEnable+{}".format(vm.name)
)
except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(
vm,
"Failed to attach audio input from {!s} to {!s}: "
"pulseaudio agent not running".format(audiovm, vm),
)
else:
audiovm.untrusted_qdb.write("/audio-input/{}".format(vm.name), "1")

# pylint: disable=unused-argument
@qubes.ext.handler("device-pre-detach:mic")
Expand All @@ -153,13 +160,53 @@ async def on_device_pre_detach_mic(self, vm, event, device):
raise qubes.exc.QubesVMNotRunningError(
audiovm, "Audio VM {} isn't running".format(audiovm)
)
try:
await audiovm.run_service_for_stdio(
"qubes.AudioInputDisable+{}".format(vm.name)

if audiovm.features.check_with_netvm(
"supported-rpc.qubes.AudioInputDisable", False
):
try:
await audiovm.run_service_for_stdio(
"qubes.AudioInputDisable+{}".format(vm.name)
)
except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(
vm,
"Failed to detach audio input from {!s} to {!s}: "
"pulseaudio agent not running".format(audiovm, vm),
)
else:
audiovm.untrusted_qdb.write("/audio-input/{}".format(vm.name), "0")

@qubes.ext.handler("property-set:audiovm")
def on_property_set(self, subject, event, name, newvalue, oldvalue=None):
if not subject.is_running() or not newvalue:
return
if not newvalue.is_running():
subject.log.warning(
"Cannot attach mic to {!s}: "
"AudioVM '{!s}' is powered off.".format(subject, newvalue)
)
if newvalue == oldvalue:
return
if oldvalue and oldvalue.is_running():
mic_allowed = oldvalue.untrusted_qdb.read(
"/audio-input/{}".format(subject.name)
)
except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(
vm,
"Failed to detach audio input from {!s} to {!s}: "
"pulseaudio agent not running".format(audiovm, vm),
mic_allowed_value = (
mic_allowed.decode("utf-8")
if mic_allowed is not None
else None
)
if not mic_allowed_value:
return
if mic_allowed_value in ("0", "1"):
newvalue.untrusted_qdb.write(
"/audio-input/{}".format(subject.name), mic_allowed_value
)
else:
raise qubes.exc.QubesVMError(
subject,
"Invalid value '{!s}' for '/audio-input/{!s}' from {!s}".format(
mic_allowed_value, subject.name, oldvalue
),
)
2 changes: 0 additions & 2 deletions rpm_spec/gui-daemon.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,6 @@ rm -f %{name}-%{version}

%files -n qubes-audio-daemon
/usr/bin/pacat-simple-vchan
/etc/qubes-rpc/qubes.AudioInputEnable
/etc/qubes-rpc/qubes.AudioInputDisable

%files -n qubes-gui-dom0
%config(noreplace) %{_sysconfdir}/qubes/policy.d/90-default-gui-daemon.policy
Expand Down
1 change: 1 addition & 0 deletions window-icon-updater/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ all:

install:
install -D icon-receiver $(DESTDIR)/usr/lib/qubes/icon-receiver
install -d $(DESTDIR)/etc/qubes-rpc
ln -s /var/run/qubes/icon-receiver.sock $(DESTDIR)/etc/qubes-rpc/qubes.WindowIconUpdater
install -d $(DESTDIR)/etc/qubes/rpc-config
install -m 0664 -D qubes.WindowIconUpdater.config $(DESTDIR)/etc/qubes/rpc-config/qubes.WindowIconUpdater
Expand Down

0 comments on commit 844ce83

Please sign in to comment.