Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-15-SP7:GA
pacemaker.12602
bsc#1131353-bsc#1131356-0005-High-pacemakerd-vs...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File bsc#1131353-bsc#1131356-0005-High-pacemakerd-vs.-IPC-procfs-confused-deputy-authe.patch of Package pacemaker.12602
From 052e6045eea77685aabeed12c519c7c9eb9b5287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com> Date: Tue, 16 Apr 2019 00:13:31 +0200 Subject: [PATCH 5/7] High: pacemakerd vs. IPC/procfs confused deputy authenticity issue (3/4) [3/4: other daemons to authenticate IPC servers of fellow processes] Now that CVE-2018-16877 issue alone is still only partially covered based on the preceding commits in the set, put the server-by-client authentication (enabled and 1/3 and partially sported in 2/3) into practice widely amongst the communicating pacemaker child daemons and towards CPG API provided by 3rd party but principally using the same underlying IPC mechanism facilitated by libqb, and consequently close the remaining "big gap". As a small justification to introducing yet another "return value" int variable, type-correctness is restored for those that shall be cs_error_t to begin with. --- daemons/pacemakerd/pcmkd_corosync.c | 61 +++++++++++- lib/cluster/corosync.c | 178 ++++++++++++++++++++++++++++++------ lib/cluster/cpg.c | 81 +++++++++++++--- lib/common/ipc.c | 43 ++++++++- 4 files changed, 317 insertions(+), 46 deletions(-) Index: pacemaker-1.1.18+20180430.b12c320f5/daemons/pacemakerd/pcmkd_corosync.c =================================================================== --- pacemaker-1.1.18+20180430.b12c320f5.orig/daemons/pacemakerd/pcmkd_corosync.c +++ pacemaker-1.1.18+20180430.b12c320f5/daemons/pacemakerd/pcmkd_corosync.c @@ -1,5 +1,7 @@ /* - * Copyright 2010-2018 Andrew Beekhof <andrew@beekhof.net> + * Copyright 2010-2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. @@ -21,8 +23,11 @@ #include <corosync/cmap.h> #include <crm/cluster/internal.h> +#include <crm/common/ipc.h> /* for crm_ipc_is_authentic_process */ #include <crm/common/mainloop.h> +#include <crm/common/ipc_internal.h> /* PCMK__SPECIAL_PID* */ + enum cluster_type_e stack = pcmk_cluster_unknown; static corosync_cfg_handle_t cfg_handle; @@ -91,7 +96,10 @@ gboolean cluster_connect_cfg(uint32_t * nodeid) { cs_error_t rc; - int fd = 0, retries = 0; + int fd = -1, retries = 0, rv; + uid_t found_uid = 0; + gid_t found_gid = 0; + pid_t found_pid = 0; static struct mainloop_fd_callbacks cfg_fd_callbacks = { .dispatch = pcmk_cfg_dispatch, @@ -101,13 +109,27 @@ cluster_connect_cfg(uint32_t * nodeid) cs_repeat(retries, 30, rc = corosync_cfg_initialize(&cfg_handle, &cfg_callbacks)); if (rc != CS_OK) { - crm_err("corosync cfg init error %d", rc); + crm_err("corosync cfg init: %s (%d)", cs_strerror(rc), rc); return FALSE; } rc = corosync_cfg_fd_get(cfg_handle, &fd); if (rc != CS_OK) { - crm_err("corosync cfg fd_get error %d", rc); + crm_err("corosync cfg fd_get: %s (%d)", cs_strerror(rc), rc); + goto bail; + } + + /* CFG provider run as root (in given user namespace, anyway)? */ + if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid, + &found_uid, &found_gid))) { + crm_err("CFG provider is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + goto bail; + } else if (rv < 0) { + crm_err("Could not verify authenticity of CFG provider: %s (%d)", + strerror(-rv), -rv); goto bail; } @@ -152,10 +174,15 @@ get_config_opt(uint64_t unused, cmap_han gboolean mcp_read_config(void) { - int rc = CS_OK; + cs_error_t rc = CS_OK; int retries = 0; cmap_handle_t local_handle; uint64_t config = 0; + int fd = -1; + uid_t found_uid = 0; + gid_t found_gid = 0; + pid_t found_pid = 0; + int rv; // There can be only one possibility do { @@ -178,6 +205,30 @@ mcp_read_config(void) return FALSE; } + rc = cmap_fd_get(local_handle, &fd); + if (rc != CS_OK) { + crm_err("Could not obtain the CMAP API connection: %s (%d)", + cs_strerror(rc), rc); + cmap_finalize(local_handle); + return FALSE; + } + + /* CMAP provider run as root (in given user namespace, anyway)? */ + if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid, + &found_uid, &found_gid))) { + crm_err("CMAP provider is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + cmap_finalize(local_handle); + return FALSE; + } else if (rv < 0) { + crm_err("Could not verify authenticity of CMAP provider: %s (%d)", + strerror(-rv), -rv); + cmap_finalize(local_handle); + return FALSE; + } + stack = get_cluster_type(); crm_info("Reading configure for stack: %s", name_for_cluster_type(stack)); Index: pacemaker-1.1.18+20180430.b12c320f5/lib/cluster/corosync.c =================================================================== --- pacemaker-1.1.18+20180430.b12c320f5.orig/lib/cluster/corosync.c +++ pacemaker-1.1.18+20180430.b12c320f5/lib/cluster/corosync.c @@ -1,19 +1,10 @@ /* - * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> + * Copyright 2004-2019 the Pacemaker project contributors * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. + * The version control history for this file may have further details. * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include <crm_internal.h> @@ -40,6 +31,8 @@ #include <crm/msg_xml.h> +#include <crm/common/ipc_internal.h> /* PCMK__SPECIAL_PID* */ + quorum_handle_t pcmk_quorum_handle = 0; gboolean(*quorum_app_callback) (unsigned long long seq, gboolean quorate) = NULL; @@ -65,10 +58,15 @@ char * corosync_node_name(uint64_t /*cmap_handle_t */ cmap_handle, uint32_t nodeid) { int lpc = 0; - int rc = CS_OK; + cs_error_t rc = CS_OK; int retries = 0; char *name = NULL; cmap_handle_t local_handle = 0; + int fd = -1; + uid_t found_uid = 0; + gid_t found_gid = 0; + pid_t found_pid = 0; + int rv; if (nodeid == 0) { nodeid = get_local_nodeid(0); @@ -97,6 +95,27 @@ corosync_node_name(uint64_t /*cmap_handl if (cmap_handle == 0) { cmap_handle = local_handle; + + rc = cmap_fd_get(cmap_handle, &fd); + if (rc != CS_OK) { + crm_err("Could not obtain the CMAP API connection: %s (%d)", + cs_strerror(rc), rc); + goto bail; + } + + /* CMAP provider run as root (in given user namespace, anyway)? */ + if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid, + &found_uid, &found_gid))) { + crm_err("CMAP provider is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + goto bail; + } else if (rv < 0) { + crm_err("Could not verify authenticity of CMAP provider: %s (%d)", + strerror(-rv), -rv); + goto bail; + } } while (name == NULL && cmap_handle != 0) { @@ -137,6 +156,7 @@ corosync_node_name(uint64_t /*cmap_handl lpc++; } +bail: if(local_handle) { cmap_finalize(local_handle); } @@ -248,11 +268,15 @@ gboolean cluster_connect_quorum(gboolean(*dispatch) (unsigned long long, gboolean), void (*destroy) (gpointer)) { - int rc = -1; + cs_error_t rc; int fd = 0; int quorate = 0; uint32_t quorum_type = 0; struct mainloop_fd_callbacks quorum_fd_callbacks; + uid_t found_uid = 0; + gid_t found_gid = 0; + pid_t found_pid = 0; + int rv; quorum_fd_callbacks.dispatch = pcmk_quorum_dispatch; quorum_fd_callbacks.destroy = destroy; @@ -261,7 +285,8 @@ cluster_connect_quorum(gboolean(*dispatc rc = quorum_initialize(&pcmk_quorum_handle, &quorum_callbacks, &quorum_type); if (rc != CS_OK) { - crm_err("Could not connect to the Quorum API: %d", rc); + crm_err("Could not connect to the Quorum API: %s (%d)", + cs_strerror(rc), rc); goto bail; } else if (quorum_type != QUORUM_SET) { @@ -269,6 +294,29 @@ cluster_connect_quorum(gboolean(*dispatc goto bail; } + rc = quorum_fd_get(pcmk_quorum_handle, &fd); + if (rc != CS_OK) { + crm_err("Could not obtain the Quorum API connection: %s (%d)", + strerror(rc), rc); + goto bail; + } + + /* Quorum provider run as root (in given user namespace, anyway)? */ + if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid, + &found_uid, &found_gid))) { + crm_err("Quorum provider is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + rc = CS_ERR_ACCESS; + goto bail; + } else if (rv < 0) { + crm_err("Could not verify authenticity of Quorum provider: %s (%d)", + strerror(-rv), -rv); + rc = CS_ERR_ACCESS; + goto bail; + } + rc = quorum_getquorate(pcmk_quorum_handle, &quorate); if (rc != CS_OK) { crm_err("Could not obtain the current Quorum API state: %d", rc); @@ -289,12 +337,6 @@ cluster_connect_quorum(gboolean(*dispatc goto bail; } - rc = quorum_fd_get(pcmk_quorum_handle, &fd); - if (rc != CS_OK) { - crm_err("Could not obtain the Quorum API connection: %d", rc); - goto bail; - } - mainloop_add_fd("quorum", G_PRIORITY_HIGH, fd, dispatch, &quorum_fd_callbacks); corosync_initialize_nodelist(NULL, FALSE, NULL); @@ -485,10 +527,15 @@ gboolean corosync_initialize_nodelist(void *cluster, gboolean force_member, xmlNode * xml_parent) { int lpc = 0; - int rc = CS_OK; + cs_error_t rc = CS_OK; int retries = 0; gboolean any = FALSE; cmap_handle_t cmap_handle; + int fd = -1; + uid_t found_uid = 0; + gid_t found_gid = 0; + pid_t found_pid = 0; + int rv; do { rc = cmap_initialize(&cmap_handle); @@ -506,6 +553,27 @@ corosync_initialize_nodelist(void *clust return FALSE; } + rc = cmap_fd_get(cmap_handle, &fd); + if (rc != CS_OK) { + crm_err("Could not obtain the CMAP API connection: %s (%d)", + cs_strerror(rc), rc); + goto bail; + } + + /* CMAP provider run as root (in given user namespace, anyway)? */ + if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid, + &found_uid, &found_gid))) { + crm_err("CMAP provider is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + goto bail; + } else if (rv < 0) { + crm_err("Could not verify authenticity of CMAP provider: %s (%d)", + strerror(-rv), -rv); + goto bail; + } + crm_peer_init(); crm_trace("Initializing corosync nodelist"); for (lpc = 0; TRUE; lpc++) { @@ -559,6 +627,7 @@ corosync_initialize_nodelist(void *clust free(name); } +bail: cmap_finalize(cmap_handle); return any; } @@ -568,36 +637,68 @@ corosync_cluster_name(void) { cmap_handle_t handle; char *cluster_name = NULL; - int rc = CS_OK; + cs_error_t rc = CS_OK; + int fd = -1; + uid_t found_uid = 0; + gid_t found_gid = 0; + pid_t found_pid = 0; + int rv; rc = cmap_initialize(&handle); if (rc != CS_OK) { - crm_info("Failed to initialize the cmap API: %s (%d)", ais_error2text(rc), rc); + crm_info("Failed to initialize the cmap API: %s (%d)", + cs_strerror(rc), rc); return NULL; } + rc = cmap_fd_get(handle, &fd); + if (rc != CS_OK) { + crm_err("Could not obtain the CMAP API connection: %s (%d)", + cs_strerror(rc), rc); + goto bail; + } + + /* CMAP provider run as root (in given user namespace, anyway)? */ + if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid, + &found_uid, &found_gid))) { + crm_err("CMAP provider is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + goto bail; + } else if (rv < 0) { + crm_err("Could not verify authenticity of CMAP provider: %s (%d)", + strerror(-rv), -rv); + goto bail; + } + rc = cmap_get_string(handle, "totem.cluster_name", &cluster_name); if (rc != CS_OK) { - crm_info("Cannot get totem.cluster_name: %s (%d)", ais_error2text(rc), rc); + crm_info("Cannot get totem.cluster_name: %s (%d)", cs_strerror(rc), rc); } else { crm_debug("cmap totem.cluster_name = '%s'", cluster_name); } +bail: cmap_finalize(handle); - return cluster_name; } int corosync_cmap_has_config(const char *prefix) { - int rc = CS_OK; + cs_error_t rc = CS_OK; int retries = 0; static int found = -1; cmap_handle_t cmap_handle; cmap_iter_handle_t iter_handle; char key_name[CMAP_KEYNAME_MAXLEN + 1]; + int fd = -1; + uid_t found_uid = 0; + gid_t found_gid = 0; + pid_t found_pid = 0; + int rv; if(found != -1) { return found; @@ -620,6 +721,27 @@ corosync_cmap_has_config(const char *pre return -1; } + rc = cmap_fd_get(cmap_handle, &fd); + if (rc != CS_OK) { + crm_err("Could not obtain the CMAP API connection: %s (%d)", + cs_strerror(rc), rc); + goto bail; + } + + /* CMAP provider run as root (in given user namespace, anyway)? */ + if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid, + &found_uid, &found_gid))) { + crm_err("CMAP provider is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + goto bail; + } else if (rv < 0) { + crm_err("Could not verify authenticity of CMAP provider: %s (%d)", + strerror(-rv), -rv); + goto bail; + } + rc = cmap_iter_init(cmap_handle, prefix, &iter_handle); if (rc != CS_OK) { crm_warn("Failed to initialize iteration for corosync cmap '%s': %s (rc=%d)", Index: pacemaker-1.1.18+20180430.b12c320f5/lib/cluster/cpg.c =================================================================== --- pacemaker-1.1.18+20180430.b12c320f5.orig/lib/cluster/cpg.c +++ pacemaker-1.1.18+20180430.b12c320f5/lib/cluster/cpg.c @@ -1,5 +1,7 @@ /* - * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> + * Copyright 2004-2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -38,6 +40,8 @@ #include <crm/msg_xml.h> +#include <crm/common/ipc_internal.h> /* PCMK__SPECIAL_PID* */ + cpg_handle_t pcmk_cpg_handle = 0; /* TODO: Remove, use cluster.cpg_handle */ static bool cpg_evicted = FALSE; @@ -71,11 +75,16 @@ cluster_disconnect_cpg(crm_cluster_t *cl uint32_t get_local_nodeid(cpg_handle_t handle) { - int rc = CS_OK; + cs_error_t rc = CS_OK; int retries = 0; static uint32_t local_nodeid = 0; cpg_handle_t local_handle = handle; cpg_callbacks_t cb = { }; + int fd = -1; + uid_t found_uid = 0; + gid_t found_gid = 0; + pid_t found_pid = 0; + int rv; if(local_nodeid != 0) { return local_nodeid; @@ -84,6 +93,32 @@ uint32_t get_local_nodeid(cpg_handle_t h if(handle == 0) { crm_trace("Creating connection"); cs_repeat(retries, 5, rc = cpg_initialize(&local_handle, &cb)); + if (rc != CS_OK) { + crm_err("Could not connect to the CPG API: %s (%d)", + cs_strerror(rc), rc); + return 0; + } + + rc = cpg_fd_get(local_handle, &fd); + if (rc != CS_OK) { + crm_err("Could not obtain the CPG API connection: %s (%d)", + cs_strerror(rc), rc); + goto bail; + } + + /* CPG provider run as root (in given user namespace, anyway)? */ + if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid, + &found_uid, &found_gid))) { + crm_err("CPG provider is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + goto bail; + } else if (rv < 0) { + crm_err("Could not verify authenticity of CPG provider: %s (%d)", + strerror(-rv), -rv); + goto bail; + } } if (rc == CS_OK) { @@ -95,6 +130,8 @@ uint32_t get_local_nodeid(cpg_handle_t h if (rc != CS_OK) { crm_err("Could not get local node id from the CPG API: %s (%d)", ais_error2text(rc), rc); } + +bail: if(handle == 0) { crm_trace("Closing connection"); cpg_finalize(local_handle); @@ -409,12 +446,16 @@ pcmk_cpg_membership(cpg_handle_t handle, gboolean cluster_connect_cpg(crm_cluster_t *cluster) { - int rc = -1; - int fd = 0; + cs_error_t rc; + int fd = -1; int retries = 0; uint32_t id = 0; crm_node_t *peer = NULL; cpg_handle_t handle = 0; + uid_t found_uid = 0; + gid_t found_gid = 0; + pid_t found_pid = 0; + int rv; struct mainloop_fd_callbacks cpg_fd_callbacks = { .dispatch = pcmk_cpg_dispatch, @@ -439,7 +480,31 @@ cluster_connect_cpg(crm_cluster_t *clust cs_repeat(retries, 30, rc = cpg_initialize(&handle, &cpg_callbacks)); if (rc != CS_OK) { - crm_err("Could not connect to the Cluster Process Group API: %d", rc); + crm_err("Could not connect to the CPG API: %s (%d)", + cs_strerror(rc), rc); + goto bail; + } + + rc = cpg_fd_get(handle, &fd); + if (rc != CS_OK) { + crm_err("Could not obtain the CPG API connection: %s (%d)", + cs_strerror(rc), rc); + goto bail; + } + + /* CPG provider run as root (in given user namespace, anyway)? */ + if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid, + &found_uid, &found_gid))) { + crm_err("CPG provider is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + rc = CS_ERR_ACCESS; + goto bail; + } else if (rv < 0) { + crm_err("Could not verify authenticity of CPG provider: %s (%d)", + strerror(-rv), -rv); + rc = CS_ERR_ACCESS; goto bail; } @@ -458,12 +523,6 @@ cluster_connect_cpg(crm_cluster_t *clust goto bail; } - rc = cpg_fd_get(handle, &fd); - if (rc != CS_OK) { - crm_err("Could not obtain the CPG API connection: %d", rc); - goto bail; - } - pcmk_cpg_handle = handle; cluster->cpg_handle = handle; mainloop_add_fd("corosync-cpg", G_PRIORITY_MEDIUM, fd, cluster, &cpg_fd_callbacks); Index: pacemaker-1.1.18+20180430.b12c320f5/lib/common/ipc.c =================================================================== --- pacemaker-1.1.18+20180430.b12c320f5.orig/lib/common/ipc.c +++ pacemaker-1.1.18+20180430.b12c320f5/lib/common/ipc.c @@ -890,11 +890,18 @@ crm_ipc_new(const char *name, size_t max * * \param[in] client Connection instance obtained from crm_ipc_new() * - * \return TRUE on success, FALSE otherwise (in which case errno will be set) + * \return TRUE on success, FALSE otherwise (in which case errno will be set; + * specifically, in case of discovering the remote side is not + * authentic, its value is set to ECONNABORTED). */ bool crm_ipc_connect(crm_ipc_t * client) { + static uid_t cl_uid = 0; + static gid_t cl_gid = 0; + pid_t found_pid = 0; uid_t found_uid = 0; gid_t found_gid = 0; + int rv; + client->need_reply = FALSE; client->ipc = qb_ipcc_connect(client->name, client->buf_size); @@ -905,7 +912,39 @@ crm_ipc_connect(crm_ipc_t * client) client->pfd.fd = crm_ipc_get_fd(client); if (client->pfd.fd < 0) { - crm_debug("Could not obtain file descriptor for %s connection: %s (%d)", client->name, pcmk_strerror(errno), errno); + rv = errno; + /* message already omitted */ + crm_ipc_close(client); + errno = rv; + return FALSE; + } + + if (!cl_uid && !cl_gid + && (rv = crm_user_lookup(CRM_DAEMON_USER, &cl_uid, &cl_gid)) < 0) { + errno = -rv; + /* message already omitted */ + crm_ipc_close(client); + errno = -rv; + return FALSE; + } + + if (!(rv = crm_ipc_is_authentic_process(client->pfd.fd, cl_uid, cl_gid, + &found_pid, &found_uid, + &found_gid))) { + crm_err("Daemon (IPC %s) is not authentic:" + " process %lld (uid: %lld, gid: %lld)", + client->name, (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) found_gid); + crm_ipc_close(client); + errno = ECONNABORTED; + return FALSE; + + } else if (rv < 0) { + errno = -rv; + crm_perror(LOG_ERR, "Could not verify authenticity of daemon (IPC %s)", + client->name); + crm_ipc_close(client); + errno = -rv; return FALSE; }
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor