1// SPDX-License-Identifier: GPL-2.0
2/* Use watch_queue API to watch for notifications.
3 *
4 * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
5 * Written by David Howells (dhowells@redhat.com)
6 */
7
8#define _GNU_SOURCE
9#include <stdbool.h>
10#include <stdarg.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <signal.h>
15#include <unistd.h>
16#include <errno.h>
17#include <sys/ioctl.h>
18#include <limits.h>
19#include <linux/watch_queue.h>
20#include <linux/unistd.h>
21#include <linux/keyctl.h>
22
23#ifndef KEYCTL_WATCH_KEY
24#define KEYCTL_WATCH_KEY -1
25#endif
26#ifndef __NR_keyctl
27#define __NR_keyctl -1
28#endif
29
30#define BUF_SIZE 256
31
32static long keyctl_watch_key(int key, int watch_fd, int watch_id)
33{
34	return syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, watch_fd, watch_id);
35}
36
37static const char *key_subtypes[256] = {
38	[NOTIFY_KEY_INSTANTIATED]	= "instantiated",
39	[NOTIFY_KEY_UPDATED]		= "updated",
40	[NOTIFY_KEY_LINKED]		= "linked",
41	[NOTIFY_KEY_UNLINKED]		= "unlinked",
42	[NOTIFY_KEY_CLEARED]		= "cleared",
43	[NOTIFY_KEY_REVOKED]		= "revoked",
44	[NOTIFY_KEY_INVALIDATED]	= "invalidated",
45	[NOTIFY_KEY_SETATTR]		= "setattr",
46};
47
48static void saw_key_change(struct watch_notification *n, size_t len)
49{
50	struct key_notification *k = (struct key_notification *)n;
51
52	if (len != sizeof(struct key_notification)) {
53		fprintf(stderr, "Incorrect key message length\n");
54		return;
55	}
56
57	printf("KEY %08x change=%u[%s] aux=%u\n",
58	       k->key_id, n->subtype, key_subtypes[n->subtype], k->aux);
59}
60
61/*
62 * Consume and display events.
63 */
64static void consumer(int fd)
65{
66	unsigned char buffer[433], *p, *end;
67	union {
68		struct watch_notification n;
69		unsigned char buf1[128];
70	} n;
71	ssize_t buf_len;
72
73	for (;;) {
74		buf_len = read(fd, buffer, sizeof(buffer));
75		if (buf_len == -1) {
76			perror("read");
77			exit(1);
78		}
79
80		if (buf_len == 0) {
81			printf("-- END --\n");
82			return;
83		}
84
85		if (buf_len > sizeof(buffer)) {
86			fprintf(stderr, "Read buffer overrun: %zd\n", buf_len);
87			return;
88		}
89
90		printf("read() = %zd\n", buf_len);
91
92		p = buffer;
93		end = buffer + buf_len;
94		while (p < end) {
95			size_t largest, len;
96
97			largest = end - p;
98			if (largest > 128)
99				largest = 128;
100			if (largest < sizeof(struct watch_notification)) {
101				fprintf(stderr, "Short message header: %zu\n", largest);
102				return;
103			}
104			memcpy(&n, p, largest);
105
106			printf("NOTIFY[%03zx]: ty=%06x sy=%02x i=%08x\n",
107			       p - buffer, n.n.type, n.n.subtype, n.n.info);
108
109			len = n.n.info & WATCH_INFO_LENGTH;
110			if (len < sizeof(n.n) || len > largest) {
111				fprintf(stderr, "Bad message length: %zu/%zu\n", len, largest);
112				exit(1);
113			}
114
115			switch (n.n.type) {
116			case WATCH_TYPE_META:
117				switch (n.n.subtype) {
118				case WATCH_META_REMOVAL_NOTIFICATION:
119					printf("REMOVAL of watchpoint %08x\n",
120					       (n.n.info & WATCH_INFO_ID) >>
121					       WATCH_INFO_ID__SHIFT);
122					break;
123				case WATCH_META_LOSS_NOTIFICATION:
124					printf("-- LOSS --\n");
125					break;
126				default:
127					printf("other meta record\n");
128					break;
129				}
130				break;
131			case WATCH_TYPE_KEY_NOTIFY:
132				saw_key_change(&n.n, len);
133				break;
134			default:
135				printf("other type\n");
136				break;
137			}
138
139			p += len;
140		}
141	}
142}
143
144static struct watch_notification_filter filter = {
145	.nr_filters	= 1,
146	.filters = {
147		[0]	= {
148			.type			= WATCH_TYPE_KEY_NOTIFY,
149			.subtype_filter[0]	= UINT_MAX,
150		},
151	},
152};
153
154int main(int argc, char **argv)
155{
156	int pipefd[2], fd;
157
158	if (pipe2(pipefd, O_NOTIFICATION_PIPE) == -1) {
159		perror("pipe2");
160		exit(1);
161	}
162	fd = pipefd[0];
163
164	if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE) == -1) {
165		perror("watch_queue(size)");
166		exit(1);
167	}
168
169	if (ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1) {
170		perror("watch_queue(filter)");
171		exit(1);
172	}
173
174	if (keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01) == -1) {
175		perror("keyctl");
176		exit(1);
177	}
178
179	if (keyctl_watch_key(KEY_SPEC_USER_KEYRING, fd, 0x02) == -1) {
180		perror("keyctl");
181		exit(1);
182	}
183
184	consumer(fd);
185	exit(0);
186}
187