/*
- * Copyright (C) 2017 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ * Copyright (C) 2017 Jérémie Galarneau <jeremie.galarneau@efficios.com>
*
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License, version 2 only, as
- * published by the Free Software Foundation.
+ * SPDX-License-Identifier: GPL-2.0-only
*
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program; if not, write to the Free Software Foundation, Inc., 51
- * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _LGPL_SOURCE
};
struct notification_client {
+ notification_client_id id;
int socket;
/* Client protocol version. */
uint8_t major, minor;
*/
struct cds_list_head condition_list;
struct cds_lfht_node client_socket_ht_node;
+ struct cds_lfht_node client_id_ht_node;
struct {
+ /*
+ * If a client's communication is inactive, it means a fatal
+ * error (either a protocol error or the socket API returned
+ * a fatal error). No further communication should be attempted;
+ * the client is queued for clean-up.
+ */
+ bool active;
struct {
/*
* During the reception of a message, the reception
static
-int match_client(struct cds_lfht_node *node, const void *key)
+int match_client_socket(struct cds_lfht_node *node, const void *key)
{
/* This double-cast is intended to supress pointer-to-cast warning. */
- int socket = (int) (intptr_t) key;
- struct notification_client *client;
+ const int socket = (int) (intptr_t) key;
+ const struct notification_client *client = caa_container_of(node,
+ struct notification_client, client_socket_ht_node);
- client = caa_container_of(node, struct notification_client,
- client_socket_ht_node);
+ return client->socket == socket;
+}
+
+static
+int match_client_id(struct cds_lfht_node *node, const void *key)
+{
+ /* This double-cast is intended to supress pointer-to-cast warning. */
+ const notification_client_id id = *((notification_client_id *) key);
+ const struct notification_client *client = caa_container_of(
+ node, struct notification_client, client_id_ht_node);
- return !!(client->socket == socket);
+ return client->id == id;
}
static
return key_hash ^ domain_hash;
}
+static
+unsigned long hash_client_socket(int socket)
+{
+ return hash_key_ulong((void *) (unsigned long) socket, lttng_ht_seed);
+}
+
+static
+unsigned long hash_client_id(notification_client_id id)
+{
+ return hash_key_u64(&id, lttng_ht_seed);
+}
+
/*
* Get the type of object to which a given condition applies. Bindings let
* the notification system evaluate a trigger's condition when a given
case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW:
case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH:
case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE:
- return LTTNG_OBJECT_TYPE_CHANNEL;
+ return LTTNG_OBJECT_TYPE_CHANNEL;
case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING:
case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED:
return LTTNG_OBJECT_TYPE_SESSION;
&iter);
node = cds_lfht_iter_get_node(&iter);
- return node ? caa_container_of(node,
+ return node ? caa_container_of(node,
struct notification_client_list,
notification_trigger_ht_node) : NULL;
}
ret = -1;
goto end;
}
-
+ if (ret) {
+ /* Fatal error. */
+ goto end;
+ }
if (!evaluation) {
/* Evaluation yielded nothing. Normal exit. */
DBG("[notification-thread] Newly subscribed-to condition evaluated to false, nothing to report to client");
if (client->socket >= 0) {
(void) lttcomm_close_unix_sock(client->socket);
+ client->socket = -1;
}
lttng_dynamic_buffer_reset(&client->communication.inbound.buffer);
lttng_dynamic_buffer_reset(&client->communication.outbound.buffer);
struct notification_client *client = NULL;
cds_lfht_lookup(state->client_socket_ht,
- hash_key_ulong((void *) (unsigned long) socket, lttng_ht_seed),
- match_client,
+ hash_client_socket(socket),
+ match_client_socket,
(void *) (unsigned long) socket,
&iter);
node = cds_lfht_iter_get_node(&iter);
return client;
}
+/*
+ * Call with rcu_read_lock held (and hold for the lifetime of the returned
+ * client pointer).
+ */
+static
+struct notification_client *get_client_from_id(notification_client_id id,
+ struct notification_thread_state *state)
+{
+ struct cds_lfht_iter iter;
+ struct cds_lfht_node *node;
+ struct notification_client *client = NULL;
+
+ cds_lfht_lookup(state->client_id_ht,
+ hash_client_id(id),
+ match_client_id,
+ &id,
+ &iter);
+ node = cds_lfht_iter_get_node(&iter);
+ if (!node) {
+ goto end;
+ }
+
+ client = caa_container_of(node, struct notification_client,
+ client_id_ht_node);
+end:
+ return client;
+}
+
static
bool buffer_usage_condition_applies_to_channel(
const struct lttng_condition *condition,
goto end;
}
- list = caa_container_of(node,
+ list = caa_container_of(node,
struct lttng_session_trigger_list,
session_triggers_ht_node);
end:
* buffers. Therefore, we reject triggers that require that
* mechanism to be available to be evaluated.
*/
- ret = kernel_supports_ring_buffer_snapshot_sample_positions(
- kernel_tracer_fd);
+ ret = kernel_supports_ring_buffer_snapshot_sample_positions();
break;
}
default:
free(trigger_ht_element);
error:
if (free_trigger) {
- struct lttng_action *action = lttng_trigger_get_action(trigger);
-
- lttng_condition_destroy(condition);
- lttng_action_destroy(action);
lttng_trigger_destroy(trigger);
}
rcu_read_unlock();
struct lttng_trigger_ht_element *trigger_ht_element = NULL;
struct lttng_condition *condition = lttng_trigger_get_condition(
trigger);
- struct lttng_action *action;
enum lttng_error_code cmd_reply;
rcu_read_lock();
struct lttng_trigger_ht_element, node);
cds_lfht_del(state->triggers_ht, triggers_ht_node);
- condition = lttng_trigger_get_condition(trigger_ht_element->trigger);
- lttng_condition_destroy(condition);
- action = lttng_trigger_get_action(trigger_ht_element->trigger);
- lttng_action_destroy(action);
+ /* Release the ownership of the trigger. */
lttng_trigger_destroy(trigger_ht_element->trigger);
call_rcu(&trigger_ht_element->rcu_node, free_lttng_trigger_ht_element_rcu);
end:
return -1;
}
-static
-unsigned long hash_client_socket(int socket)
-{
- return hash_key_ulong((void *) (unsigned long) socket, lttng_ht_seed);
-}
-
static
int socket_set_non_blocking(int socket)
{
ret = -1;
goto error;
}
+ client->id = state->next_notification_client_id++;
CDS_INIT_LIST_HEAD(&client->condition_list);
lttng_dynamic_buffer_init(&client->communication.inbound.buffer);
lttng_dynamic_buffer_init(&client->communication.outbound.buffer);
ret = client_reset_inbound_state(client);
if (ret) {
ERR("[notification-thread] Failed to reset client communication's inbound state");
- ret = 0;
+ ret = 0;
goto error;
}
cds_lfht_add(state->client_socket_ht,
hash_client_socket(client->socket),
&client->client_socket_ht_node);
+ cds_lfht_add(state->client_id_ht,
+ hash_client_id(client->id),
+ &client->client_id_ht_node);
rcu_read_unlock();
return ret;
if (ret) {
ERR("[notification-thread] Failed to remove client socket from poll set");
}
- cds_lfht_del(state->client_socket_ht,
+ cds_lfht_del(state->client_socket_ht,
&client->client_socket_ht_node);
+ cds_lfht_del(state->client_id_ht,
+ &client->client_id_ht_node);
notification_client_destroy(client, state);
end:
rcu_read_unlock();
struct cds_lfht_iter iter;
struct lttng_trigger_ht_element *trigger_ht_element;
+ rcu_read_lock();
cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element,
node) {
int ret = handle_notification_thread_command_unregister_trigger(
error_occurred = true;
}
}
+ rcu_read_unlock();
return error_occurred ? -1 : 0;
}
ret = lttcomm_send_unix_sock_non_block(client->socket,
client->communication.outbound.buffer.data,
to_send_count);
- if ((ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) ||
- (ret > 0 && ret < to_send_count)) {
+ if ((ret >= 0 && ret < to_send_count)) {
DBG("[notification-thread] Client (socket fd = %i) outgoing queue could not be completely flushed",
client->socket);
to_send_count -= max(ret, 0);
goto end;
}
client->validated = true;
+ client->communication.active = true;
break;
}
case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE:
struct lttng_condition *condition;
enum lttng_notification_channel_status status =
LTTNG_NOTIFICATION_CHANNEL_STATUS_OK;
- const struct lttng_buffer_view condition_view =
- lttng_buffer_view_from_dynamic_buffer(
+ struct lttng_payload_view condition_view =
+ lttng_payload_view_from_dynamic_buffer(
&client->communication.inbound.buffer,
0, -1);
size_t expected_condition_size =
client->communication.inbound.buffer.size;
- ret = lttng_condition_create_from_buffer(&condition_view,
+ ret = lttng_condition_create_from_payload(&condition_view,
&condition);
if (ret != expected_condition_size) {
ERR("[notification-thread] Malformed condition received from client");
* forego this double-multiplication or it could be performed
* as fixed-point math.
*
- * Note that caching should accomodate the case where the
+ * Note that caching should accommodates the case where the
* condition applies to multiple channels (i.e. don't assume
* that all channels matching my_chann* have the same size...)
*/
}
static
-int client_enqueue_dropped_notification(struct notification_client *client,
- struct notification_thread_state *state)
+int client_enqueue_dropped_notification(struct notification_client *client)
{
int ret;
struct lttng_notification_channel_message msg = {
return ret;
}
+/*
+ * Permission checks relative to notification channel clients are performed
+ * here. Notice how object, client, and trigger credentials are involved in
+ * this check.
+ *
+ * The `object` credentials are the credentials associated with the "subject"
+ * of a condition. For instance, a `rotation completed` condition applies
+ * to a session. When that condition is met, it will produce an evaluation
+ * against a session. Hence, in this case, the `object` credentials are the
+ * credentials of the "subject" session.
+ *
+ * The `trigger` credentials are the credentials of the user that registered the
+ * trigger.
+ *
+ * The `client` credentials are the credentials of the user that created a given
+ * notification channel.
+ *
+ * In terms of visibility, it is expected that non-privilieged users can only
+ * register triggers against "their" objects (their own sessions and
+ * applications they are allowed to interact with). They can then open a
+ * notification channel and subscribe to notifications associated with those
+ * triggers.
+ *
+ * As for privilieged users, they can register triggers against the objects of
+ * other users. They can then subscribe to the notifications associated to their
+ * triggers. Privilieged users _can't_ subscribe to the notifications of
+ * triggers owned by other users; they must create their own triggers.
+ *
+ * This is more a concern of usability than security. It would be difficult for
+ * a root user reliably subscribe to a specific set of conditions without
+ * interference from external users (those could, for instance, unregister
+ * their triggers).
+ */
static
int send_evaluation_to_clients(const struct lttng_trigger *trigger,
const struct lttng_evaluation *evaluation,
struct notification_client_list* client_list,
struct notification_thread_state *state,
- uid_t channel_uid, gid_t channel_gid)
+ uid_t object_uid, gid_t object_gid)
{
int ret = 0;
- struct lttng_dynamic_buffer msg_buffer;
+ struct lttng_payload msg_payload;
struct notification_client_list_element *client_list_element, *tmp;
const struct lttng_notification notification = {
.condition = (struct lttng_condition *) lttng_trigger_get_const_condition(trigger),
struct lttng_notification_channel_message msg_header = {
.type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION,
};
+ const struct lttng_credentials *trigger_creds = lttng_trigger_get_credentials(trigger);
- lttng_dynamic_buffer_init(&msg_buffer);
+ lttng_payload_init(&msg_payload);
- ret = lttng_dynamic_buffer_append(&msg_buffer, &msg_header,
+ ret = lttng_dynamic_buffer_append(&msg_payload.buffer, &msg_header,
sizeof(msg_header));
if (ret) {
goto end;
}
- ret = lttng_notification_serialize(¬ification, &msg_buffer);
+ ret = lttng_notification_serialize(¬ification, &msg_payload);
if (ret) {
ERR("[notification-thread] Failed to serialize notification");
ret = -1;
}
/* Update payload size. */
- ((struct lttng_notification_channel_message * ) msg_buffer.data)->size =
- (uint32_t) (msg_buffer.size - sizeof(msg_header));
+ ((struct lttng_notification_channel_message * ) msg_payload.buffer.data)->size =
+ (uint32_t) (msg_payload.buffer.size - sizeof(msg_header));
cds_list_for_each_entry_safe(client_list_element, tmp,
&client_list->list, node) {
struct notification_client *client =
client_list_element->client;
- if (client->uid != channel_uid && client->gid != channel_gid &&
+ if (client->uid != object_uid && client->gid != object_gid &&
client->uid != 0) {
/* Client is not allowed to monitor this channel. */
- DBG("[notification-thread] Skipping client at it does not have the permission to receive notification for this channel");
+ DBG("[notification-thread] Skipping client at it does not have the object permission to receive notification for this trigger");
+ continue;
+ }
+
+ if (client->uid != trigger_creds->uid && client->gid != trigger_creds->gid) {
+ DBG("[notification-thread] Skipping client at it does not have the permission to receive notification for this trigger");
continue;
}
DBG("[notification-thread] Sending notification to client (fd = %i, %zu bytes)",
- client->socket, msg_buffer.size);
+ client->socket, msg_payload.buffer.size);
if (client->communication.outbound.buffer.size) {
/*
* Outgoing data is already buffered for this client;
if (!client->communication.outbound.dropped_notification) {
client->communication.outbound.dropped_notification = true;
ret = client_enqueue_dropped_notification(
- client, state);
+ client);
if (ret) {
goto end;
}
ret = lttng_dynamic_buffer_append_buffer(
&client->communication.outbound.buffer,
- &msg_buffer);
+ &msg_payload.buffer);
if (ret) {
goto end;
}
}
ret = 0;
end:
- lttng_dynamic_buffer_reset(&msg_buffer);
+ lttng_payload_reset(&msg_payload);
return ret;
}
trigger_list = caa_container_of(node, struct lttng_channel_trigger_list,
channel_triggers_ht_node);
cds_list_for_each_entry(trigger_list_element, &trigger_list->list,
- node) {
+ node) {
const struct lttng_condition *condition;
const struct lttng_action *action;
const struct lttng_trigger *trigger;