timescaledb/cmake/GenerateScripts.cmake
Sven Klemm c8b8516e46 Fix extension installation privilege escalation
TimescaleDB was vulnerable to a privilege escalation attack in
the extension installation script. An attacker could precreate
objects normally owned by the extension and get those objects
used in the installation script since the script would only try
to create them if they did not already exist. Thanks to Pedro
Gallegos for reporting the problem.

This patch changes the schema, table and function creation to fail
and abort the installation when the object already exists instead
of using the existing object.

Security: CVE-2022-24128
2022-02-09 17:53:20 +01:00

199 lines
6.7 KiB
CMake

# Functions and macros to generate upgrade and downgrade scripts
# concatenate_files(<output> <file> ...)
#
# Concatenate a list of files into <output>
function(concatenate_files OUTPUT_FILE FIRST_FILE)
file(READ ${FIRST_FILE} _contents)
file(WRITE ${OUTPUT_FILE} "${_contents}")
foreach(_file ${ARGN})
file(READ ${_file} _contents)
file(APPEND ${OUTPUT_FILE} "${_contents}")
endforeach()
endfunction()
# generate_script(...)
#
# Generate a script file and install it into the script directory.
#
# VERSION <version>
#
# Version to use for shared libraryx.
#
# SCRIPT <file>
#
# File name for script file.
#
# OUTPUT_DIRECTORY <dir>
#
# Directory for where script file should be written. Defaults to
# CMAKE_CURRENT_BINARY_DIR.
#
# FILES <file> ...
#
# List of files to include, in order, in the script file.
function(generate_script)
set(options)
set(oneValueArgs VERSION SCRIPT OUTPUT_DIRECTORY)
set(multiValueArgs FILES)
cmake_parse_arguments(_generate "${options}" "${oneValueArgs}"
"${multiValueArgs}" ${ARGN})
if(NOT _generate_OUTPUT_DIRECTORY)
set(_generate_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
endif()
# Set the necessary variables, generate the real files, and append the output
# to the final update script. Ideally, the template files should have a '.in'
# suffix so that we can keep the template file and the final file available
# for debugging, but for some reason it was decided to not use the '.in'
# suffix for template files so now we're stuck with it.
set(MODULE_PATHNAME "$libdir/timescaledb-${_generate_VERSION}")
set(LOADER_PATHNAME "$libdir/timescaledb")
# Process all files. They are template files and should end with '.in' but for
# some reason they do not, so we append '.gen' instead to indicate generated
# files.
set(_result_files)
foreach(_file ${_generate_FILES})
configure_file(${_file} ${_file}.gen @ONLY)
list(APPEND _result_files ${_file}.gen)
endforeach()
# Concatenate the real files into the update file.
message(STATUS "Generating script ${_generate_SCRIPT}")
concatenate_files(${_generate_OUTPUT_DIRECTORY}/${_generate_SCRIPT}
${_result_files})
install(FILES ${_generate_OUTPUT_DIRECTORY}/${_generate_SCRIPT}
DESTINATION "${PG_SHAREDIR}/extension")
endfunction()
# generate_downgrade_script(<options>)
#
# Create a downgrade script from a source version to a target version. To figure
# out what files are necessary, the ScriptFiles.cmake manifest is read from the
# target version. If that file does not exist in the target version, the
# manifest in the current version is used, but it is assumed that files are only
# added. This situation only occur in the first version where we start to
# generate downgrade scripts and in this case files were only added, so we can
# safely ignore them.
#
# SOURCE_VERSION <version>
#
# Version to generate downgrade script from.
#
# TARGET_VERSION <version>
#
# Version to generate downgrade script to.
#
# OUTPUT_DIRECTORY <dir>
#
# Output directory for script file. Defaults to CMAKE_CURRENT_BINARY_DIR.
#
# INPUT_DIRECTORY <dir>
#
# Input directory for downgrade files. Defaults to CMAKE_CURRENT_SOURCE_DIR.
#
# FILES <file> ...
#
# Files to include, in order, when generating the downgrade script.
#
# The downgrade script is generated by creating a new file of the format
# "timescaledb--<current>--<version>.sql" consisting of the sequence of files:
#
# 1. Generated prolog from the target version
# 2. Generated downgrade files from the source version
# 3. Generated epilog from the target version
#
# The files provided are assumed to be configure templates and configure_files
# will be run on them with the following variables set:
#
# LOADER_PATHNAME: Pathname to loader shared library. This is the same as in the
# update.
#
# MODULE_PATHNAME: Pathname to timescale extension of the version being
# dowgraded to. This can be used to load "old" functions from the correct
# library.
function(generate_downgrade_script)
set(options)
set(oneValueArgs SOURCE_VERSION TARGET_VERSION OUTPUT_DIRECTORY
INPUT_DIRECTORY FILES)
cmake_parse_arguments(_downgrade "${options}" "${oneValueArgs}"
"${multiValueArgs}" ${ARGN})
if(NOT _downgrade_OUTPUT_DIRECTORY)
set(_downgrade_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
endif()
if(NOT _downgrade_INPUT_DIRECTORY)
set(_downgrade_INPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif()
foreach(_downgrade_file ${_downgrade_FILES})
if(NOT EXISTS ${_downgrade_INPUT_DIRECTORY}/${_downgrade_file})
message(FATAL_ERROR "No downgrade file ${_downgrade_file} found!")
endif()
endforeach()
# Fetch manifest with list of files for the prolog and epilog from the target
# version, if we are in a version that supports downgrades. Otherwise, take
# the one in the current version.
#
# We have a specific exception where we allow a missing manifest for the first
# version that supports downgrades and assume that the files to include are
# the same in the target version as the current one.
if(_downgrade_TARGET_VERSION VERSION_GREATER 2.3)
git_versioned_get(VERSION ${_downgrade_TARGET_VERSION} FILES
${CMAKE_SOURCE_DIR}/cmake/ScriptFiles.cmake)
else()
file(MAKE_DIRECTORY
"${CMAKE_BINARY_DIR}/v${_downgrade_TARGET_VERSION}/cmake")
file(COPY "${CMAKE_SOURCE_DIR}/cmake/ScriptFiles.cmake"
DESTINATION "${CMAKE_BINARY_DIR}/v${_downgrade_TARGET_VERSION}/cmake")
endif()
# This will include the variables in this scope, but not in the parent scope
# so we can read them locally without affecting the parent scope.
include(
${CMAKE_BINARY_DIR}/v${_downgrade_TARGET_VERSION}/cmake/ScriptFiles.cmake)
set(_downgrade_PRE_FILES ${PRE_UPDATE_FILES})
set(_downgrade_POST_FILES "${PRE_INSTALL_FUNCTION_FILES};${SOURCE_FILES}" ${SET_POST_UPDATE_STAGE}
${POST_UPDATE_FILES} ${UNSET_UPDATE_STAGE})
# Fetch prolog and epilog from target version.
git_versioned_get(
VERSION
${_downgrade_TARGET_VERSION}
FILES
${_downgrade_PRE_FILES}
RESULT_FILES
_prolog_files
IGNORE_ERRORS)
git_versioned_get(
VERSION
${_downgrade_TARGET_VERSION}
FILES
${_downgrade_POST_FILES}
RESULT_FILES
_epilog_files
IGNORE_ERRORS)
set(_files ${_prolog_files})
foreach(_downgrade_file ${_downgrade_FILES})
list(APPEND _files ${_downgrade_INPUT_DIRECTORY}/${_downgrade_file})
endforeach()
list(APPEND _files ${_epilog_files})
generate_script(
VERSION
${_downgrade_TARGET_VERSION}
SCRIPT
timescaledb--${_downgrade_SOURCE_VERSION}--${_downgrade_TARGET_VERSION}.sql
OUTPUT_DIRECTORY
${_downgrade_OUTPUT_DIRECTORY}
FILES
${_files})
endfunction()