Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
23ca5d0
Potential fix for code scanning alert no. 974: File created without r…
achamayou Jun 2, 2026
3024f5b
Harden remaining file creation sites
Copilot Jun 2, 2026
c5e20f3
Address validation feedback on secure file writes
Copilot Jun 2, 2026
974d919
Polish secure file write helper handling
Copilot Jun 2, 2026
ae39220
Merge branch 'main' into alert-autofix-974
achamayou Jun 2, 2026
0fa1219
Modernize files::dump overloads
Copilot Jun 2, 2026
3962663
Fix dump overload docstring
Copilot Jun 2, 2026
a0c3230
Use std::as_bytes in dump helpers
Copilot Jun 2, 2026
5a63f6a
Align dump helper overload naming
Copilot Jun 2, 2026
01e41ea
Format files helper header
Copilot Jun 2, 2026
87b80c0
Suppress clang-tidy fclose warning
Copilot Jun 2, 2026
6a3d31f
Potential fix for pull request finding
achamayou Jun 3, 2026
29eb7d3
Potential fix for pull request finding
achamayou Jun 3, 2026
3c35a8b
Merge branch 'main' into alert-autofix-974
achamayou Jun 3, 2026
97b68fd
Add changelog entry for restrictive file permissions (0600) on host-c…
Copilot Jun 3, 2026
734556a
Merge branch 'main' into alert-autofix-974
achamayou Jun 3, 2026
44e83bc
Rename saved_errno to fdopen_errno for clarity in open_file()
Copilot Jun 3, 2026
1a8ef37
Merge remote-tracking branch 'origin/main' into alert-autofix-974
Copilot Jun 5, 2026
d55fada
Merge branch 'main' into alert-autofix-974
maxtropets Jun 8, 2026
cc863b7
Move security changelog entry to new [7.0.5] block and bump pyproject…
Copilot Jun 8, 2026
a19c350
Add PR number (#7916) to changelog security entry
Copilot Jun 8, 2026
089b417
Merge branch 'main' into alert-autofix-974
achamayou Jun 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [7.0.5]

[7.0.5]: https://github.com/microsoft/CCF/releases/tag/ccf-7.0.5

### Security

- Host-created files (ledger chunks, snapshots, PID file, and node certificate/key files) are now created with restrictive permissions (`0600`) instead of relying on the process `umask`. Existing deployments will not see existing files affected; only newly created files will have these restricted permissions (#7916).

## [7.0.4]

[7.0.4]: https://github.com/microsoft/CCF/releases/tag/ccf-7.0.4
Expand Down
2 changes: 1 addition & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ccf"
version = "7.0.4"
version = "7.0.5"
authors = [
{ name="CCF Team", email="CCF-Sec@microsoft.com" },
]
Expand Down
106 changes: 92 additions & 14 deletions src/ds/files.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@
// Licensed under the Apache 2.0 License.
#pragma once

#include <cerrno>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <fstream>
#include <glob.h>
#include <iostream>
#include <nlohmann/json.hpp>
#include <optional>
#include <span>
#include <sstream>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <unistd.h>
#include <vector>

#define FMT_HEADER_ONLY
Expand All @@ -19,6 +28,45 @@
namespace files
{
namespace fs = std::filesystem;
static constexpr mode_t private_file_permissions = S_IRUSR | S_IWUSR;

static int open_fd(
const fs::path& file,
int flags,
mode_t permissions = private_file_permissions)
{
return ::open(file.c_str(), flags, permissions);
}

static FILE* open_file(
const fs::path& file,
int flags,
const char* mode,
mode_t permissions = private_file_permissions)
{
const auto fd = open_fd(file, flags, permissions);
if (fd == -1)
{
return nullptr;
}

errno = 0;
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
auto* f = fdopen(fd, mode);
if (f == nullptr)
{
auto fdopen_errno = errno;
// Preserve the original fdopen() failure when it set errno, and only
// fall back to close()'s errno if fdopen() did not.
if (close(fd) != 0 && fdopen_errno == 0)
{
fdopen_errno = errno;
}
errno = fdopen_errno != 0 ? fdopen_errno : EIO;
}

return f;
}

/**
* @brief Checks if a path exists
Expand Down Expand Up @@ -114,32 +162,62 @@ namespace files
return nlohmann::json::parse(v.begin(), v.end());
}

static void dump(std::span<const std::byte> data, const fs::path& file)
{
auto* f = open_file(file, O_WRONLY | O_CREAT | O_TRUNC, "wb");
if (f == nullptr)
{
throw std::logic_error(fmt::format(
"Failed to open file {} for writing: {}",
file.string(),
std::strerror(errno))); // NOLINT(concurrency-mt-unsafe)
}

errno = 0;
const auto bytes_written =
fwrite(data.data(), sizeof(std::byte), data.size(), f);
const auto write_errno = errno;
errno = 0;
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
const auto close_rc = fclose(f);
const auto close_errno = errno;
if (bytes_written != data.size() || close_rc != 0)
{
if (bytes_written != data.size())
{
errno = write_errno != 0 ? write_errno : EIO;
}
else
{
errno = close_errno != 0 ? close_errno : EIO;
}
throw std::logic_error(fmt::format(
"Failed to write to file {}: {}",
file.string(),
std::strerror(errno))); // NOLINT(concurrency-mt-unsafe)
}
}

/**
* @brief Writes the content of a vector to a file
* @brief Writes the content of a byte span to a file
*
* @param data vector to write
* @param data bytes to write
* @param file the path
*/
static void dump(const std::vector<uint8_t>& data, const std::string& file)
static void dump(std::span<const uint8_t> data, const fs::path& file)
{
using namespace std;
ofstream f(file, ios::binary | ios::trunc);
f.write(reinterpret_cast<const char*>(data.data()), data.size());
if (!f)
{
throw logic_error("Failed to write to file: " + file);
}
dump(std::as_bytes(data), file);
}
Comment thread
achamayou marked this conversation as resolved.

/**
* @brief Writes the content of a string to a file
* @brief Writes the content of a string view to a file
*
* @param data string to write
* @param data string view to write
* @param file the path
*/
static void dump(const std::string& data, const std::string& file)
static void dump(std::string_view data, const fs::path& file)
{
dump(std::vector<uint8_t>(data.begin(), data.end()), file);
dump(std::as_bytes(std::span(data)), file);
}
Comment thread
achamayou marked this conversation as resolved.

static void rename(const fs::path& src, const fs::path& dst)
Expand Down
10 changes: 4 additions & 6 deletions src/host/ledger.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
#include <map>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <uv.h>
#include <vector>

Expand Down Expand Up @@ -120,13 +119,12 @@ namespace asynchost

auto file_path = dir / file_name;

// Use exclusive-create mode ("x") to atomically fail if the file
// already exists, avoiding a separate fs::exists() stat call.
// Use O_EXCL to atomically fail if the file already exists, and create
// with restrictive permissions (0600) rather than relying on umask.
{
TimeBoundLogger log_if_slow(
fmt::format("Creating ledger file - fopen({})", file_path));
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
file = fopen(file_path.c_str(), "w+bx");
fmt::format("Creating ledger file - open({})", file_path));
file = files::open_file(file_path, O_RDWR | O_CREAT | O_EXCL, "w+b");
}
if (file == nullptr)
{
Expand Down
10 changes: 3 additions & 7 deletions src/host/lfs_file_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
// Licensed under the Apache 2.0 License.
#pragma once

#include "ds/files.h"
#include "ds/messaging.h"
#include "indexing/lfs_ringbuffer_types.h"
#include "time_bound_logger.h"

#include <filesystem>
#include <fstream>

namespace asynchost
{
Expand Down Expand Up @@ -50,16 +50,12 @@ namespace asynchost
const auto target_path = root_dir / key;
{
TimeBoundLogger log_if_slow(fmt::format(
"Writing LFS file ({} bytes) - ofstream({})",
"Writing LFS file ({} bytes) - {}",
encrypted.size(),
target_path));
std::ofstream f(target_path, std::ios::trunc | std::ios::binary);
LOG_TRACE_FMT(
"Writing {} byte file to {}", encrypted.size(), target_path);
f.write(
reinterpret_cast<char const*>(encrypted.data()),
encrypted.size());
f.close();
files::dump(encrypted, target_path);
}
});

Expand Down
5 changes: 3 additions & 2 deletions src/snapshots/snapshot_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "ccf/ds/nonstd.h"
#include "consensus/ledger_enclave_types.h"
#include "ds/files.h"
#include "host/time_bound_logger.h"
#include "snapshots/filenames.h"

Expand Down Expand Up @@ -185,8 +186,8 @@ namespace snapshots
{
asynchost::TimeBoundLogger log_open_if_slow(
fmt::format("Opening snapshot file - open({})", file_name));
snapshot_fd = open(
full_snapshot_path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0664);
snapshot_fd =
files::open_fd(full_snapshot_path, O_CREAT | O_EXCL | O_WRONLY);
}
if (snapshot_fd == -1)
{
Expand Down