diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx index fb53e0e273..65e675f7a1 100644 --- a/Source/cmFileCommand.cxx +++ b/Source/cmFileCommand.cxx @@ -3017,7 +3017,7 @@ bool HandleLockCommand(std::vector const& args, cmSystemTools::SetFatalErrorOccurred(); return false; } - FILE* file = cmsys::SystemTools::Fopen(path, "w"); + FILE* file = cmsys::SystemTools::Fopen(path, "a"); if (!file) { status.GetMakefile().IssueMessage( MessageType::FATAL_ERROR, diff --git a/Tests/RunCMake/file/LOCK-symlink-no-truncate-stdout.txt b/Tests/RunCMake/file/LOCK-symlink-no-truncate-stdout.txt new file mode 100644 index 0000000000..93605d2ee0 --- /dev/null +++ b/Tests/RunCMake/file/LOCK-symlink-no-truncate-stdout.txt @@ -0,0 +1,4 @@ +-- Original content: IMPORTANT DATA THAT MUST NOT BE DESTROYED +-- Lock result: 0 +-- Final content: IMPORTANT DATA THAT MUST NOT BE DESTROYED +-- PASS: Symlink target was not truncated diff --git a/Tests/RunCMake/file/LOCK-symlink-no-truncate.cmake b/Tests/RunCMake/file/LOCK-symlink-no-truncate.cmake new file mode 100644 index 0000000000..10aa428a2b --- /dev/null +++ b/Tests/RunCMake/file/LOCK-symlink-no-truncate.cmake @@ -0,0 +1,37 @@ +# Test that file(LOCK) does not truncate existing files when the lock path +# is a symlink pointing to another file. This is a regression test for a +# data destruction vulnerability (CWE-59). + +set(target_file "${CMAKE_CURRENT_BINARY_DIR}/target_file.txt") +set(lock_symlink "${CMAKE_CURRENT_BINARY_DIR}/lock_symlink") + +# Create a target file with known content +file(WRITE "${target_file}" "IMPORTANT DATA THAT MUST NOT BE DESTROYED") + +# Read original content for comparison +file(READ "${target_file}" original_content) +message(STATUS "Original content: ${original_content}") + +# Create a symlink pointing to the target file +file(CREATE_LINK "${target_file}" "${lock_symlink}" SYMBOLIC) + +# Attempt to lock the symlink - this should NOT truncate the target +file(LOCK "${lock_symlink}" RESULT_VARIABLE lock_result) +message(STATUS "Lock result: ${lock_result}") + +# Release the lock +file(LOCK "${lock_symlink}" RELEASE) + +# Verify the target file still has its content +file(READ "${target_file}" final_content) +message(STATUS "Final content: ${final_content}") + +if(NOT final_content STREQUAL original_content) + message(FATAL_ERROR + "VULNERABILITY: file(LOCK) truncated the symlink target!\n" + "Original: '${original_content}'\n" + "Final: '${final_content}'" + ) +endif() + +message(STATUS "PASS: Symlink target was not truncated") diff --git a/Tests/RunCMake/file/RunCMakeTest.cmake b/Tests/RunCMake/file/RunCMakeTest.cmake index 57c191bf53..7220278266 100644 --- a/Tests/RunCMake/file/RunCMakeTest.cmake +++ b/Tests/RunCMake/file/RunCMakeTest.cmake @@ -91,6 +91,8 @@ if(NOT WIN32 if(NOT CYGWIN) run_cmake(INSTALL-FOLLOW_SYMLINK_CHAIN) endif() + # Test that file(LOCK) doesn't truncate symlink targets (CVE regression test) + run_cmake(LOCK-symlink-no-truncate) endif() run_cmake(REAL_PATH-non-existing)