smbd: Claim version in g_lock
authorVolker Lendecke <vl@samba.org>
Mon, 22 May 2017 14:00:08 +0000 (16:00 +0200)
committerVolker Lendecke <vl@samba.org>
Thu, 15 Jun 2017 11:19:15 +0000 (13:19 +0200)
Protect smbd against version incompatibilities in a cluster.

At first startup smbd locks "samba_version_string" and writes its version
string. It then downgrades the lock to a read lock. Subsequent smbds check
against the version string and also keep the read lock around. If the version
does not match, we try to write our own version. But as there's a read lock,
the lock upgrade to write lock will fail due the read lock being around. So as
long as there's one smbd with this read lock, no other version of smbd will be
able to start.

Signed-off-by: Volker Lendecke <vl@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
docs-xml/smbdotconf/misc/allowunsafeclusterupgrade.xml [new file with mode: 0644]
source3/smbd/server.c

diff --git a/docs-xml/smbdotconf/misc/allowunsafeclusterupgrade.xml b/docs-xml/smbdotconf/misc/allowunsafeclusterupgrade.xml
new file mode 100644 (file)
index 0000000..02398ff
--- /dev/null
@@ -0,0 +1,16 @@
+<samba:parameter name="allow unsafe cluster upgrade"
+                 context="G"
+                 type="boolean"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+       <para>If set to no (the default), smbd checks at startup if
+       other smbd versions are running in the cluster and refuses to
+       start if so. This is done to protect data corruption in
+       internal data structures due to incompatible Samba versions
+       running concurrently in the same cluster. Setting this
+       parameter to <value type="example">yes</value> disables this
+       safety check.
+       </para>
+</description>
+<value type="default">no</value>
+</samba:parameter>
index 494e188308be0ceccb989873b9cff09c54a4dcf8..4883c4ec510b544b04be6a6a3af85e1a91a35fc6 100644 (file)
@@ -53,6 +53,7 @@
 #include "smbd/smbd_cleanupd.h"
 #include "lib/util/sys_rw.h"
 #include "cleanupdb.h"
+#include "g_lock.h"
 
 #ifdef CLUSTER_SUPPORT
 #include "ctdb_protocol.h"
@@ -1442,6 +1443,110 @@ static void smbd_parent_sig_hup_handler(struct tevent_context *ev,
        printing_subsystem_update(parent->ev_ctx, parent->msg_ctx, true);
 }
 
+struct smbd_claim_version_state {
+       TALLOC_CTX *mem_ctx;
+       char *version;
+};
+
+static void smbd_claim_version_parser(const struct g_lock_rec *locks,
+                                     size_t num_locks,
+                                     const uint8_t *data,
+                                     size_t datalen,
+                                     void *private_data)
+{
+       struct smbd_claim_version_state *state = private_data;
+
+       if (datalen == 0) {
+               state->version = NULL;
+               return;
+       }
+       if (data[datalen-1] != '\0') {
+               DBG_WARNING("Invalid samba version\n");
+               dump_data(DBGLVL_WARNING, data, datalen);
+               state->version = NULL;
+               return;
+       }
+       state->version = talloc_strdup(state->mem_ctx, (const char *)data);
+}
+
+static NTSTATUS smbd_claim_version(struct messaging_context *msg,
+                                  const char *version)
+{
+       const char *name = "samba_version_string";
+       struct smbd_claim_version_state state;
+       struct g_lock_ctx *ctx;
+       NTSTATUS status;
+
+       ctx = g_lock_ctx_init(msg, msg);
+       if (ctx == NULL) {
+               DBG_WARNING("g_lock_ctx_init failed\n");
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+
+       status = g_lock_lock(ctx, name, G_LOCK_READ,
+                            (struct timeval) { .tv_sec = 60 });
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_WARNING("g_lock_lock(G_LOCK_READ) failed: %s\n",
+                           nt_errstr(status));
+               TALLOC_FREE(ctx);
+               return status;
+       }
+
+       state = (struct smbd_claim_version_state) { .mem_ctx = ctx };
+
+       status = g_lock_dump(ctx, name, smbd_claim_version_parser, &state);
+       if (!NT_STATUS_IS_OK(status) &&
+           !NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+               DBG_ERR("Could not read samba_version_string\n");
+               g_lock_unlock(ctx, name);
+               TALLOC_FREE(ctx);
+               return status;
+       }
+
+       if ((state.version != NULL) && (strcmp(version, state.version) == 0)) {
+               /*
+                * Leave the read lock for us around. Someone else already
+                * set the version correctly
+                */
+               TALLOC_FREE(ctx);
+               return NT_STATUS_OK;
+       }
+
+       status = g_lock_lock(ctx, name, G_LOCK_WRITE,
+                            (struct timeval) { .tv_sec = 60 });
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_WARNING("g_lock_lock(G_LOCK_WRITE) failed: %s\n",
+                           nt_errstr(status));
+               DBG_ERR("smbd %s already running, refusing to start "
+                       "version %s\n", state.version, version);
+               TALLOC_FREE(ctx);
+               return NT_STATUS_SXS_VERSION_CONFLICT;
+       }
+
+       status = g_lock_write_data(ctx, name, (const uint8_t *)version,
+                                  strlen(version)+1);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_WARNING("g_lock_write_data failed: %s\n",
+                           nt_errstr(status));
+               TALLOC_FREE(ctx);
+               return status;
+       }
+
+       status = g_lock_lock(ctx, name, G_LOCK_READ,
+                            (struct timeval) { .tv_sec = 60 });
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_WARNING("g_lock_lock(G_LOCK_READ) failed: %s\n",
+                           nt_errstr(status));
+               TALLOC_FREE(ctx);
+               return status;
+       }
+
+       /*
+        * Leave "ctx" dangling so that g_lock.tdb keeps opened.
+        */
+       return NT_STATUS_OK;
+}
+
 /****************************************************************************
  main program.
 ****************************************************************************/
@@ -1905,6 +2010,15 @@ extern void build_options(bool screen);
                exit_daemon("Samba cannot init global open", map_errno_from_nt_status(status));
        }
 
+       if (lp_clustering() && !lp_allow_unsafe_cluster_upgrade()) {
+               status = smbd_claim_version(msg_ctx, samba_version_string());
+               if (!NT_STATUS_IS_OK(status)) {
+                       DBG_WARNING("Could not claim version: %s\n",
+                                   nt_errstr(status));
+                       return -1;
+               }
+       }
+
        /* This MUST be done before start_epmd() because otherwise
         * start_epmd() forks and races against dcesrv_ep_setup() to
         * call directory_create_or_exist() */