From 389fb800ac8be2832efedd19978a2b8ced37eb61 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 27 Mar 2009 17:10:34 -0400 Subject: netlabel: Label incoming TCP connections correctly in SELinux The current NetLabel/SELinux behavior for incoming TCP connections works but only through a series of happy coincidences that rely on the limited nature of standard CIPSO (only able to convey MLS attributes) and the write equality imposed by the SELinux MLS constraints. The problem is that network sockets created as the result of an incoming TCP connection were not on-the-wire labeled based on the security attributes of the parent socket but rather based on the wire label of the remote peer. The issue had to do with how IP options were managed as part of the network stack and where the LSM hooks were in relation to the code which set the IP options on these newly created child sockets. While NetLabel/SELinux did correctly set the socket's on-the-wire label it was promptly cleared by the network stack and reset based on the IP options of the remote peer. This patch, in conjunction with a prior patch that adjusted the LSM hook locations, works to set the correct on-the-wire label format for new incoming connections through the security_inet_conn_request() hook. Besides the correct behavior there are many advantages to this change, the most significant is that all of the NetLabel socket labeling code in SELinux now lives in hooks which can return error codes to the core stack which allows us to finally get ride of the selinux_netlbl_inode_permission() logic which greatly simplfies the NetLabel/SELinux glue code. In the process of developing this patch I also ran into a small handful of AF_INET6 cleanliness issues that have been fixed which should make the code safer and easier to extend in the future. Signed-off-by: Paul Moore Acked-by: Casey Schaufler Signed-off-by: James Morris --- include/net/cipso_ipv4.h | 17 +++++++++++++++++ include/net/netlabel.h | 12 +++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/cipso_ipv4.h b/include/net/cipso_ipv4.h index bedc7f62e35d..abd443604c9f 100644 --- a/include/net/cipso_ipv4.h +++ b/include/net/cipso_ipv4.h @@ -40,6 +40,7 @@ #include #include #include +#include #include /* known doi values */ @@ -215,6 +216,10 @@ int cipso_v4_sock_setattr(struct sock *sk, const struct netlbl_lsm_secattr *secattr); void cipso_v4_sock_delattr(struct sock *sk); int cipso_v4_sock_getattr(struct sock *sk, struct netlbl_lsm_secattr *secattr); +int cipso_v4_req_setattr(struct request_sock *req, + const struct cipso_v4_doi *doi_def, + const struct netlbl_lsm_secattr *secattr); +void cipso_v4_req_delattr(struct request_sock *req); int cipso_v4_skbuff_setattr(struct sk_buff *skb, const struct cipso_v4_doi *doi_def, const struct netlbl_lsm_secattr *secattr); @@ -247,6 +252,18 @@ static inline int cipso_v4_sock_getattr(struct sock *sk, return -ENOSYS; } +static inline int cipso_v4_req_setattr(struct request_sock *req, + const struct cipso_v4_doi *doi_def, + const struct netlbl_lsm_secattr *secattr) +{ + return -ENOSYS; +} + +static inline void cipso_v4_req_delattr(struct request_sock *req) +{ + return; +} + static inline int cipso_v4_skbuff_setattr(struct sk_buff *skb, const struct cipso_v4_doi *doi_def, const struct netlbl_lsm_secattr *secattr) diff --git a/include/net/netlabel.h b/include/net/netlabel.h index 749011eedc0b..bdb10e5183d5 100644 --- a/include/net/netlabel.h +++ b/include/net/netlabel.h @@ -36,6 +36,7 @@ #include #include #include +#include #include struct cipso_v4_doi; @@ -406,6 +407,7 @@ int netlbl_secattr_catmap_setrng(struct netlbl_lsm_secattr_catmap *catmap, */ int netlbl_enabled(void); int netlbl_sock_setattr(struct sock *sk, + u16 family, const struct netlbl_lsm_secattr *secattr); void netlbl_sock_delattr(struct sock *sk); int netlbl_sock_getattr(struct sock *sk, @@ -413,6 +415,8 @@ int netlbl_sock_getattr(struct sock *sk, int netlbl_conn_setattr(struct sock *sk, struct sockaddr *addr, const struct netlbl_lsm_secattr *secattr); +int netlbl_req_setattr(struct request_sock *req, + const struct netlbl_lsm_secattr *secattr); int netlbl_skbuff_setattr(struct sk_buff *skb, u16 family, const struct netlbl_lsm_secattr *secattr); @@ -519,7 +523,8 @@ static inline int netlbl_enabled(void) return 0; } static inline int netlbl_sock_setattr(struct sock *sk, - const struct netlbl_lsm_secattr *secattr) + u16 family, + const struct netlbl_lsm_secattr *secattr) { return -ENOSYS; } @@ -537,6 +542,11 @@ static inline int netlbl_conn_setattr(struct sock *sk, { return -ENOSYS; } +static inline int netlbl_req_setattr(struct request_sock *req, + const struct netlbl_lsm_secattr *secattr) +{ + return -ENOSYS; +} static inline int netlbl_skbuff_setattr(struct sk_buff *skb, u16 family, const struct netlbl_lsm_secattr *secattr) -- cgit v1.2.3 From 8651d5c0b1f874c5b8307ae2b858bc40f9f02482 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 27 Mar 2009 17:10:48 -0400 Subject: lsm: Remove the socket_post_accept() hook The socket_post_accept() hook is not currently used by any in-tree modules and its existence continues to cause problems by confusing people about what can be safely accomplished using this hook. If a legitimate need for this hook arises in the future it can always be reintroduced. Signed-off-by: Paul Moore Signed-off-by: James Morris --- include/linux/security.h | 13 ------------- net/socket.c | 2 -- security/capability.c | 5 ----- security/security.c | 5 ----- 4 files changed, 25 deletions(-) (limited to 'include') diff --git a/include/linux/security.h b/include/linux/security.h index 1f2ab6353c00..54ed15799a83 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -880,11 +880,6 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts) * @sock contains the listening socket structure. * @newsock contains the newly created server socket for connection. * Return 0 if permission is granted. - * @socket_post_accept: - * This hook allows a security module to copy security - * information into the newly created socket's inode. - * @sock contains the listening socket structure. - * @newsock contains the newly created server socket for connection. * @socket_sendmsg: * Check permission before transmitting a message to another socket. * @sock contains the socket structure. @@ -1554,8 +1549,6 @@ struct security_operations { struct sockaddr *address, int addrlen); int (*socket_listen) (struct socket *sock, int backlog); int (*socket_accept) (struct socket *sock, struct socket *newsock); - void (*socket_post_accept) (struct socket *sock, - struct socket *newsock); int (*socket_sendmsg) (struct socket *sock, struct msghdr *msg, int size); int (*socket_recvmsg) (struct socket *sock, @@ -2537,7 +2530,6 @@ int security_socket_bind(struct socket *sock, struct sockaddr *address, int addr int security_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen); int security_socket_listen(struct socket *sock, int backlog); int security_socket_accept(struct socket *sock, struct socket *newsock); -void security_socket_post_accept(struct socket *sock, struct socket *newsock); int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size); int security_socket_recvmsg(struct socket *sock, struct msghdr *msg, int size, int flags); @@ -2616,11 +2608,6 @@ static inline int security_socket_accept(struct socket *sock, return 0; } -static inline void security_socket_post_accept(struct socket *sock, - struct socket *newsock) -{ -} - static inline int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { diff --git a/net/socket.c b/net/socket.c index 0b14b79c03af..91d0c0254ffe 100644 --- a/net/socket.c +++ b/net/socket.c @@ -1536,8 +1536,6 @@ SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr, fd_install(newfd, newfile); err = newfd; - security_socket_post_accept(sock, newsock); - out_put: fput_light(sock->file, fput_needed); out: diff --git a/security/capability.c b/security/capability.c index c545bd1300b5..21b6cead6a8e 100644 --- a/security/capability.c +++ b/security/capability.c @@ -620,10 +620,6 @@ static int cap_socket_accept(struct socket *sock, struct socket *newsock) return 0; } -static void cap_socket_post_accept(struct socket *sock, struct socket *newsock) -{ -} - static int cap_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { return 0; @@ -1014,7 +1010,6 @@ void security_fixup_ops(struct security_operations *ops) set_to_cap_if_null(ops, socket_connect); set_to_cap_if_null(ops, socket_listen); set_to_cap_if_null(ops, socket_accept); - set_to_cap_if_null(ops, socket_post_accept); set_to_cap_if_null(ops, socket_sendmsg); set_to_cap_if_null(ops, socket_recvmsg); set_to_cap_if_null(ops, socket_getsockname); diff --git a/security/security.c b/security/security.c index c3586c0d97e2..206e53844d2f 100644 --- a/security/security.c +++ b/security/security.c @@ -1007,11 +1007,6 @@ int security_socket_accept(struct socket *sock, struct socket *newsock) return security_ops->socket_accept(sock, newsock); } -void security_socket_post_accept(struct socket *sock, struct socket *newsock) -{ - security_ops->socket_post_accept(sock, newsock); -} - int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { return security_ops->socket_sendmsg(sock, msg, size); -- cgit v1.2.3 From 07feee8f812f7327a46186f7604df312c8c81962 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 27 Mar 2009 17:10:54 -0400 Subject: netlabel: Cleanup the Smack/NetLabel code to fix incoming TCP connections This patch cleans up a lot of the Smack network access control code. The largest changes are to fix the labeling of incoming TCP connections in a manner similar to the recent SELinux changes which use the security_inet_conn_request() hook to label the request_sock and let the label move to the child socket via the normal network stack mechanisms. In addition to the incoming TCP connection fixes this patch also removes the smk_labled field from the socket_smack struct as the minor optimization advantage was outweighed by the difficulty in maintaining it's proper state. Signed-off-by: Paul Moore Acked-by: Casey Schaufler Signed-off-by: James Morris --- include/net/netlabel.h | 5 + net/netlabel/netlabel_kapi.c | 13 +++ security/smack/smack.h | 1 - security/smack/smack_lsm.c | 260 ++++++++++++++++++++++++------------------- 4 files changed, 161 insertions(+), 118 deletions(-) (limited to 'include') diff --git a/include/net/netlabel.h b/include/net/netlabel.h index bdb10e5183d5..60ebbc1fef46 100644 --- a/include/net/netlabel.h +++ b/include/net/netlabel.h @@ -417,6 +417,7 @@ int netlbl_conn_setattr(struct sock *sk, const struct netlbl_lsm_secattr *secattr); int netlbl_req_setattr(struct request_sock *req, const struct netlbl_lsm_secattr *secattr); +void netlbl_req_delattr(struct request_sock *req); int netlbl_skbuff_setattr(struct sk_buff *skb, u16 family, const struct netlbl_lsm_secattr *secattr); @@ -547,6 +548,10 @@ static inline int netlbl_req_setattr(struct request_sock *req, { return -ENOSYS; } +static inline void netlbl_req_delattr(struct request_sock *req) +{ + return; +} static inline int netlbl_skbuff_setattr(struct sk_buff *skb, u16 family, const struct netlbl_lsm_secattr *secattr) diff --git a/net/netlabel/netlabel_kapi.c b/net/netlabel/netlabel_kapi.c index cae2f5f4cac0..b0e582f2d37a 100644 --- a/net/netlabel/netlabel_kapi.c +++ b/net/netlabel/netlabel_kapi.c @@ -860,6 +860,19 @@ req_setattr_return: return ret_val; } +/** +* netlbl_req_delattr - Delete all the NetLabel labels on a socket +* @req: the socket +* +* Description: +* Remove all the NetLabel labeling from @req. +* +*/ +void netlbl_req_delattr(struct request_sock *req) +{ + cipso_v4_req_delattr(req); +} + /** * netlbl_skbuff_setattr - Label a packet using the correct protocol * @skb: the packet diff --git a/security/smack/smack.h b/security/smack/smack.h index 64164f8fde70..5e5a3bcb599a 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -42,7 +42,6 @@ struct superblock_smack { struct socket_smack { char *smk_out; /* outbound label */ char *smk_in; /* inbound label */ - int smk_labeled; /* label scheme */ char smk_packet[SMK_LABELLEN]; /* TCP peer label */ }; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 23ad420a49aa..8ed502c2ad45 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -7,6 +7,8 @@ * Casey Schaufler * * Copyright (C) 2007 Casey Schaufler + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. + * Paul Moore * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -20,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -1275,7 +1278,6 @@ static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags) ssp->smk_in = csp; ssp->smk_out = csp; - ssp->smk_labeled = SMACK_CIPSO_SOCKET; ssp->smk_packet[0] = '\0'; sk->sk_security = ssp; @@ -1294,6 +1296,39 @@ static void smack_sk_free_security(struct sock *sk) kfree(sk->sk_security); } +/** +* smack_host_label - check host based restrictions +* @sip: the object end +* +* looks for host based access restrictions +* +* This version will only be appropriate for really small sets of single label +* hosts. The caller is responsible for ensuring that the RCU read lock is +* taken before calling this function. +* +* Returns the label of the far end or NULL if it's not special. +*/ +static char *smack_host_label(struct sockaddr_in *sip) +{ + struct smk_netlbladdr *snp; + struct in_addr *siap = &sip->sin_addr; + + if (siap->s_addr == 0) + return NULL; + + list_for_each_entry_rcu(snp, &smk_netlbladdr_list, list) + /* + * we break after finding the first match because + * the list is sorted from longest to shortest mask + * so we have found the most specific match + */ + if ((&snp->smk_host.sin_addr)->s_addr == + (siap->s_addr & (&snp->smk_mask)->s_addr)) + return snp->smk_label; + + return NULL; +} + /** * smack_set_catset - convert a capset to netlabel mls categories * @catset: the Smack categories @@ -1365,11 +1400,10 @@ static void smack_to_secattr(char *smack, struct netlbl_lsm_secattr *nlsp) */ static int smack_netlabel(struct sock *sk, int labeled) { - struct socket_smack *ssp; + struct socket_smack *ssp = sk->sk_security; struct netlbl_lsm_secattr secattr; int rc = 0; - ssp = sk->sk_security; /* * Usually the netlabel code will handle changing the * packet labeling based on the label. @@ -1393,20 +1427,44 @@ static int smack_netlabel(struct sock *sk, int labeled) bh_unlock_sock(sk); local_bh_enable(); - /* - * Remember the label scheme used so that it is not - * necessary to do the netlabel setting if it has not - * changed the next time through. - * - * The -EDESTADDRREQ case is an indication that there's - * a single level host involved. - */ - if (rc == 0) - ssp->smk_labeled = labeled; return rc; } +/** + * smack_netlbel_send - Set the secattr on a socket and perform access checks + * @sk: the socket + * @sap: the destination address + * + * Set the correct secattr for the given socket based on the destination + * address and perform any outbound access checks needed. + * + * Returns 0 on success or an error code. + * + */ +static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) +{ + int rc; + int sk_lbl; + char *hostsp; + struct socket_smack *ssp = sk->sk_security; + + rcu_read_lock(); + hostsp = smack_host_label(sap); + if (hostsp != NULL) { + sk_lbl = SMACK_UNLABELED_SOCKET; + rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE); + } else { + sk_lbl = SMACK_CIPSO_SOCKET; + rc = 0; + } + rcu_read_unlock(); + if (rc != 0) + return rc; + + return smack_netlabel(sk, sk_lbl); +} + /** * smack_inode_setsecurity - set smack xattrs * @inode: the object @@ -1488,43 +1546,6 @@ static int smack_socket_post_create(struct socket *sock, int family, return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); } - -/** - * smack_host_label - check host based restrictions - * @sip: the object end - * - * looks for host based access restrictions - * - * This version will only be appropriate for really small - * sets of single label hosts. - * - * Returns the label of the far end or NULL if it's not special. - */ -static char *smack_host_label(struct sockaddr_in *sip) -{ - struct smk_netlbladdr *snp; - struct in_addr *siap = &sip->sin_addr; - - if (siap->s_addr == 0) - return NULL; - - rcu_read_lock(); - list_for_each_entry_rcu(snp, &smk_netlbladdr_list, list) { - /* - * we break after finding the first match because - * the list is sorted from longest to shortest mask - * so we have found the most specific match - */ - if ((&snp->smk_host.sin_addr)->s_addr == - (siap->s_addr & (&snp->smk_mask)->s_addr)) { - rcu_read_unlock(); - return snp->smk_label; - } - } - rcu_read_unlock(); - return NULL; -} - /** * smack_socket_connect - connect access check * @sock: the socket @@ -1538,30 +1559,12 @@ static char *smack_host_label(struct sockaddr_in *sip) static int smack_socket_connect(struct socket *sock, struct sockaddr *sap, int addrlen) { - struct socket_smack *ssp = sock->sk->sk_security; - char *hostsp; - int rc; - if (sock->sk == NULL || sock->sk->sk_family != PF_INET) return 0; - if (addrlen < sizeof(struct sockaddr_in)) return -EINVAL; - hostsp = smack_host_label((struct sockaddr_in *)sap); - if (hostsp == NULL) { - if (ssp->smk_labeled != SMACK_CIPSO_SOCKET) - return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); - return 0; - } - - rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE); - if (rc != 0) - return rc; - - if (ssp->smk_labeled != SMACK_UNLABELED_SOCKET) - return smack_netlabel(sock->sk, SMACK_UNLABELED_SOCKET); - return 0; + return smack_netlabel_send(sock->sk, (struct sockaddr_in *)sap); } /** @@ -2262,9 +2265,6 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { struct sockaddr_in *sip = (struct sockaddr_in *) msg->msg_name; - struct socket_smack *ssp = sock->sk->sk_security; - char *hostsp; - int rc; /* * Perfectly reasonable for this to be NULL @@ -2272,22 +2272,7 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, if (sip == NULL || sip->sin_family != PF_INET) return 0; - hostsp = smack_host_label(sip); - if (hostsp == NULL) { - if (ssp->smk_labeled != SMACK_CIPSO_SOCKET) - return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); - return 0; - } - - rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE); - if (rc != 0) - return rc; - - if (ssp->smk_labeled != SMACK_UNLABELED_SOCKET) - return smack_netlabel(sock->sk, SMACK_UNLABELED_SOCKET); - - return 0; - + return smack_netlabel_send(sock->sk, sip); } @@ -2492,31 +2477,24 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, } /** - * smack_sock_graft - graft access state between two sockets - * @sk: fresh sock - * @parent: donor socket + * smack_sock_graft - Initialize a newly created socket with an existing sock + * @sk: child sock + * @parent: parent socket * - * Sets the netlabel socket state on sk from parent + * Set the smk_{in,out} state of an existing sock based on the process that + * is creating the new socket. */ static void smack_sock_graft(struct sock *sk, struct socket *parent) { struct socket_smack *ssp; - int rc; - - if (sk == NULL) - return; - if (sk->sk_family != PF_INET && sk->sk_family != PF_INET6) + if (sk == NULL || + (sk->sk_family != PF_INET && sk->sk_family != PF_INET6)) return; ssp = sk->sk_security; ssp->smk_in = ssp->smk_out = current_security(); - ssp->smk_packet[0] = '\0'; - - rc = smack_netlabel(sk, SMACK_CIPSO_SOCKET); - if (rc != 0) - printk(KERN_WARNING "Smack: \"%s\" netlbl error %d.\n", - __func__, -rc); + /* cssp->smk_packet is already set in smack_inet_csk_clone() */ } /** @@ -2531,35 +2509,82 @@ static void smack_sock_graft(struct sock *sk, struct socket *parent) static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, struct request_sock *req) { - struct netlbl_lsm_secattr skb_secattr; + u16 family = sk->sk_family; struct socket_smack *ssp = sk->sk_security; + struct netlbl_lsm_secattr secattr; + struct sockaddr_in addr; + struct iphdr *hdr; char smack[SMK_LABELLEN]; int rc; - if (skb == NULL) - return -EACCES; + /* handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; - netlbl_secattr_init(&skb_secattr); - rc = netlbl_skbuff_getattr(skb, sk->sk_family, &skb_secattr); + netlbl_secattr_init(&secattr); + rc = netlbl_skbuff_getattr(skb, family, &secattr); if (rc == 0) - smack_from_secattr(&skb_secattr, smack); + smack_from_secattr(&secattr, smack); else strncpy(smack, smack_known_huh.smk_known, SMK_MAXLEN); - netlbl_secattr_destroy(&skb_secattr); + netlbl_secattr_destroy(&secattr); + /* - * Receiving a packet requires that the other end - * be able to write here. Read access is not required. - * - * If the request is successful save the peer's label - * so that SO_PEERCRED can report it. + * Receiving a packet requires that the other end be able to write + * here. Read access is not required. */ rc = smk_access(smack, ssp->smk_in, MAY_WRITE); - if (rc == 0) - strncpy(ssp->smk_packet, smack, SMK_MAXLEN); + if (rc != 0) + return rc; + + /* + * Save the peer's label in the request_sock so we can later setup + * smk_packet in the child socket so that SO_PEERCRED can report it. + */ + req->peer_secid = smack_to_secid(smack); + + /* + * We need to decide if we want to label the incoming connection here + * if we do we only need to label the request_sock and the stack will + * propogate the wire-label to the sock when it is created. + */ + hdr = ip_hdr(skb); + addr.sin_addr.s_addr = hdr->saddr; + rcu_read_lock(); + if (smack_host_label(&addr) == NULL) { + rcu_read_unlock(); + netlbl_secattr_init(&secattr); + smack_to_secattr(smack, &secattr); + rc = netlbl_req_setattr(req, &secattr); + netlbl_secattr_destroy(&secattr); + } else { + rcu_read_unlock(); + netlbl_req_delattr(req); + } return rc; } +/** + * smack_inet_csk_clone - Copy the connection information to the new socket + * @sk: the new socket + * @req: the connection's request_sock + * + * Transfer the connection's peer label to the newly created socket. + */ +static void smack_inet_csk_clone(struct sock *sk, + const struct request_sock *req) +{ + struct socket_smack *ssp = sk->sk_security; + char *smack; + + if (req->peer_secid != 0) { + smack = smack_from_secid(req->peer_secid); + strncpy(ssp->smk_packet, smack, SMK_MAXLEN); + } else + ssp->smk_packet[0] = '\0'; +} + /* * Key management security hooks * @@ -2911,6 +2936,7 @@ struct security_operations smack_ops = { .sk_free_security = smack_sk_free_security, .sock_graft = smack_sock_graft, .inet_conn_request = smack_inet_conn_request, + .inet_csk_clone = smack_inet_csk_clone, /* key management security hooks */ #ifdef CONFIG_KEYS -- cgit v1.2.3