From 3e6393d5adfee5ef3ce8e801aa1e35e3e934db4e Mon Sep 17 00:00:00 2001
From: Ilya Verbin <iverbin@tarantool.org>
Date: Tue, 11 Oct 2022 15:50:37 +0300
Subject: [PATCH] box: use dd_version_id instead of _schema.version in
 get_version

By default a user might not have privileges to access the _schema space,
that will cause an error during schema_needs_upgrade(), which calls
get_version(). Fix this by using C variable dd_version_id, which is
updated in the _schema.version replace trigger.

There's a special case for upgrade() during bootstrap() - triggers are
disabled during bootstrap, that's why dd_version_id is not being updated.
Handle this by passing _initial_version=1.7.5 to the upgrade function.

Part of #7149

NO_DOC=internal
NO_CHANGELOG=internal
---
 extra/exports                                 |  1 +
 src/box/lua/upgrade.lua                       | 18 +++++++--------
 src/box/schema.cc                             |  7 +++++-
 src/box/schema.h                              |  5 ++++
 ...rbid_ddl_until_box_schema_upgrade_test.lua | 23 +++++++++++++++++++
 test/box/stat.result                          | 16 ++++++-------
 6 files changed, 52 insertions(+), 18 deletions(-)
 create mode 100644 test/box-luatest/gh_7149_forbid_ddl_until_box_schema_upgrade_test.lua

diff --git a/extra/exports b/extra/exports
index 0b7d2e579d..f30e7bbfe9 100644
--- a/extra/exports
+++ b/extra/exports
@@ -12,6 +12,7 @@
 base64_bufsize
 base64_decode
 base64_encode
+box_dd_version_id
 box_decimal_abs
 box_decimal_add
 box_decimal_compare
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index a077e8210f..12078fc218 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -3,6 +3,9 @@ local bit = require('bit')
 local json = require('json')
 local fio = require('fio')
 local xlog = require('xlog')
+local ffi = require('ffi')
+
+ffi.cdef('uint32_t box_dd_version_id(void);')
 
 -- Guest user id - the default user
 local GUEST = 0
@@ -1288,13 +1291,10 @@ local handlers = {
 
 -- Schema version of the snapshot.
 local function get_version()
-    local version = box.space._schema:get{'version'}
-    if version == nil then
-        error('Missing "version" in box.space._schema')
-    end
-    local major = version[2]
-    local minor = version[3]
-    local patch = version[4] or 0
+    local version = ffi.C.box_dd_version_id()
+    local major = bit.band(bit.rshift(version, 16), 0xff)
+    local minor = bit.band(bit.rshift(version, 8), 0xff)
+    local patch = bit.band(version, 0xff)
 
     return mkversion(major, minor, patch),
            string.format("%s.%s.%s", major, minor, patch)
@@ -1398,7 +1398,7 @@ local function upgrade(options)
     options = options or {}
     setmetatable(options, {__index = {auto = false}})
 
-    local version = get_version()
+    local version = options._initial_version or get_version()
     if version < mkversion(1, 6, 8) then
         log.warn('can upgrade from 1.6.8 only')
         return
@@ -1432,7 +1432,7 @@ local function bootstrap()
     -- insert initial schema
     initial_1_7_5()
     -- upgrade schema to the latest version
-    upgrade()
+    upgrade{_initial_version = mkversion(1, 7, 5)}
 
     set_system_triggers(true)
 
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 79cdaf2103..4ca145397e 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -69,13 +69,18 @@ struct rlist on_alter_func = RLIST_HEAD_INITIALIZER(on_alter_func);
 
 struct entity_access entity_access;
 
-/** Return current schema version */
 uint32_t
 box_schema_version(void)
 {
 	return schema_version;
 }
 
+uint32_t
+box_dd_version_id(void)
+{
+	return dd_version_id;
+}
+
 static int
 on_replace_dd_system_space(struct trigger *trigger, void *event)
 {
diff --git a/src/box/schema.h b/src/box/schema.h
index b5c0c13bdb..a3c505c9be 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -49,9 +49,14 @@ extern uint32_t dd_version_id;
 /** Triggers invoked after schema initialization. */
 extern struct rlist on_schema_init;
 
+/** Return current monotonic schema version. */
 uint32_t
 box_schema_version(void);
 
+/** Return current persistent schema version. */
+uint32_t
+box_dd_version_id(void);
+
 /**
  * Try to look up object name by id and type of object.
  *
diff --git a/test/box-luatest/gh_7149_forbid_ddl_until_box_schema_upgrade_test.lua b/test/box-luatest/gh_7149_forbid_ddl_until_box_schema_upgrade_test.lua
new file mode 100644
index 0000000000..6956ce6a92
--- /dev/null
+++ b/test/box-luatest/gh_7149_forbid_ddl_until_box_schema_upgrade_test.lua
@@ -0,0 +1,23 @@
+local t = require('luatest')
+local g = t.group('gh-7149')
+local server = require('test.luatest_helpers.server')
+
+g.after_each(function()
+    g.server:drop()
+end)
+
+g.before_test('test_schema_access', function()
+    g.server = server:new{alias = 'master'}
+    g.server:start()
+end)
+
+-- Check that get_version() from upgrade.lua can be executed by any user without
+-- getting an error: Read access to space '_schema' is denied for user 'user'.
+g.test_schema_access = function()
+    g.server:exec(function()
+        box.cfg()
+        box.schema.user.create('user')
+        box.session.su('user')
+        box.schema.upgrade()
+    end)
+end
diff --git a/test/box/stat.result b/test/box/stat.result
index 1ed2434107..915902c666 100644
--- a/test/box/stat.result
+++ b/test/box/stat.result
@@ -24,7 +24,7 @@ box.stat.REPLACE.total
 ...
 box.stat.SELECT.total
 ---
-- 2
+- 0
 ...
 box.stat.ERROR.total
 ---
@@ -59,7 +59,7 @@ box.stat.REPLACE.total
 ...
 box.stat.SELECT.total
 ---
-- 6
+- 4
 ...
 -- check exceptions
 space:get('Impossible value')
@@ -77,14 +77,14 @@ space:get(1)
 ...
 box.stat.SELECT.total
 ---
-- 7
+- 5
 ...
 space:get(11)
 ---
 ...
 box.stat.SELECT.total
 ---
-- 8
+- 6
 ...
 space:select(5)
 ---
@@ -92,7 +92,7 @@ space:select(5)
 ...
 box.stat.SELECT.total
 ---
-- 9
+- 7
 ...
 space:select(15)
 ---
@@ -100,14 +100,14 @@ space:select(15)
 ...
 box.stat.SELECT.total
 ---
-- 10
+- 8
 ...
 for _ in space:pairs() do end
 ---
 ...
 box.stat.SELECT.total
 ---
-- 11
+- 9
 ...
 -- reset
 box.stat.reset()
@@ -157,7 +157,7 @@ box.stat.REPLACE.total
 ...
 box.stat.SELECT.total
 ---
-- 2
+- 0
 ...
 box.stat.ERROR.total
 ---
-- 
GitLab