Skip to content

Commit 513f241

Browse files
authored
[Linux] Fix potential race when saving storage file (#35428)
* [Linux] Do not print misleading KVS init log message * [Linux] Fix race condition when saving comfiguration The mkstemp() call creates a unique file and returns file descriptor for opened file which should be used to write data. However, the previous implementation, instead of using file descriptor opened the temp file by the name, which could fail because of file removal between these calls. * Sync NuttX and WebOS implementation with Linux * Add exception * Improve readability
1 parent 9b58d4c commit 513f241

8 files changed

+228
-93
lines changed

scripts/tools/check_includes_config.py

+1
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
'src/lib/support/jsontlv/JsonToTlv.h': {'string'},
181181
'src/lib/support/jsontlv/TlvToJson.h': {'string'},
182182
'src/lib/support/jsontlv/TextFormat.h': {'string'},
183+
'src/lib/support/TemporaryFileStream.h': {'ostream', 'streambuf', 'string'},
183184
'src/app/icd/client/DefaultICDClientStorage.cpp': {'vector'},
184185
'src/app/icd/client/DefaultICDClientStorage.h': {'vector'},
185186
'src/app/icd/client/DefaultICDStorageKey.h': {'vector'},

src/lib/support/BUILD.gn

+2
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ static_library("support") {
206206
"Defer.h",
207207
"FibonacciUtils.cpp",
208208
"FibonacciUtils.h",
209+
"FileDescriptor.h",
209210
"FixedBufferAllocator.cpp",
210211
"FixedBufferAllocator.h",
211212
"Fold.h",
@@ -239,6 +240,7 @@ static_library("support") {
239240
"StringBuilder.cpp",
240241
"StringBuilder.h",
241242
"StringSplitter.h",
243+
"TemporaryFileStream.h",
242244
"ThreadOperationalDataset.cpp",
243245
"ThreadOperationalDataset.h",
244246
"TimeUtils.cpp",

src/lib/support/FileDescriptor.h

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
*
3+
* Copyright (c) 2024 Project CHIP Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#pragma once
19+
20+
#include <unistd.h>
21+
#include <utility>
22+
23+
namespace chip {
24+
25+
/// Unix file descriptor wrapper with RAII semantics.
26+
class FileDescriptor
27+
{
28+
public:
29+
FileDescriptor() = default;
30+
explicit FileDescriptor(int fd) : mFd(fd) {}
31+
~FileDescriptor() { Close(); }
32+
33+
/// Disallow copy and assignment.
34+
FileDescriptor(const FileDescriptor &) = delete;
35+
FileDescriptor & operator=(const FileDescriptor &) = delete;
36+
37+
FileDescriptor(FileDescriptor && other) noexcept : mFd(other.Release()) {}
38+
FileDescriptor & operator=(FileDescriptor && other) noexcept
39+
{
40+
Close();
41+
mFd = other.Release();
42+
return *this;
43+
}
44+
45+
int Get() const { return mFd; }
46+
47+
int Release() { return std::exchange(mFd, -1); }
48+
49+
int Close()
50+
{
51+
if (mFd != -1)
52+
{
53+
return close(std::exchange(mFd, -1));
54+
}
55+
return 0;
56+
}
57+
58+
private:
59+
int mFd = -1;
60+
};
61+
62+
} // namespace chip

src/lib/support/TemporaryFileStream.h

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
*
3+
* Copyright (c) 2024 Project CHIP Authors
4+
* All rights reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <cstdlib>
22+
#include <ostream>
23+
#include <streambuf>
24+
#include <string>
25+
#include <unistd.h>
26+
27+
#include <lib/support/FileDescriptor.h>
28+
29+
namespace chip {
30+
namespace DeviceLayer {
31+
namespace Internal {
32+
33+
class FileDescriptorStreamBuf : public std::streambuf
34+
{
35+
public:
36+
FileDescriptorStreamBuf() = default;
37+
explicit FileDescriptorStreamBuf(int fd) : mFd(fd) {}
38+
39+
FileDescriptorStreamBuf(FileDescriptorStreamBuf &) = delete;
40+
FileDescriptorStreamBuf & operator=(FileDescriptorStreamBuf &) = delete;
41+
42+
FileDescriptorStreamBuf(FileDescriptorStreamBuf && other) = default;
43+
FileDescriptorStreamBuf & operator=(FileDescriptorStreamBuf && other) = default;
44+
45+
protected:
46+
int overflow(int c) override
47+
{
48+
if (c != EOF)
49+
{
50+
char z = c;
51+
if (write(mFd, &z, 1) != 1)
52+
{
53+
return EOF;
54+
}
55+
}
56+
return c;
57+
}
58+
59+
std::streamsize xsputn(const char * s, std::streamsize n) override { return write(mFd, s, static_cast<size_t>(n)); }
60+
61+
private:
62+
int mFd = -1;
63+
};
64+
65+
/// File stream for a temporary file compatible with std::ostream.
66+
class TemporaryFileStream : public std::ostream
67+
{
68+
public:
69+
TemporaryFileStream() : std::ostream(&mBuf) {}
70+
explicit TemporaryFileStream(std::string nameTemplate) : std::ostream(&mBuf) { Open(std::move(nameTemplate)); };
71+
72+
/// Disallow copy and assignment.
73+
TemporaryFileStream(const TemporaryFileStream &) = delete;
74+
TemporaryFileStream & operator=(const TemporaryFileStream &) = delete;
75+
76+
/// Open a temporary file with a given name template.
77+
///
78+
/// In order to check if the file was opened successfully, use IsOpen().
79+
void Open(std::string nameTemplate)
80+
{
81+
mFileName = std::move(nameTemplate);
82+
mFd = FileDescriptor(mkstemp(mFileName.data()));
83+
mBuf = FileDescriptorStreamBuf(mFd.Get());
84+
}
85+
86+
/// Check if the file was opened successfully.
87+
///
88+
/// In case of failure, the error can be retrieved using errno.
89+
bool IsOpen() const { return mFd.Get() != -1; };
90+
91+
/// Synchronize the file's contents with the underlying storage device.
92+
///
93+
/// In case of failure, the error can be retrieved using errno.
94+
bool DataSync() { return fdatasync(mFd.Get()) == 0; }
95+
96+
/// Get the name of created temporary file.
97+
const std::string & GetFileName() const { return mFileName; }
98+
99+
private:
100+
FileDescriptor mFd;
101+
FileDescriptorStreamBuf mBuf;
102+
std::string mFileName;
103+
};
104+
105+
} // namespace Internal
106+
} // namespace DeviceLayer
107+
} // namespace chip

src/platform/Linux/CHIPLinuxStorage.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,15 @@ CHIP_ERROR ChipLinuxStorage::Init(const char * configFile)
5353
{
5454
CHIP_ERROR retval = CHIP_NO_ERROR;
5555

56-
ChipLogDetail(DeviceLayer, "ChipLinuxStorage::Init: Using KVS config file: %s", StringOrNullMarker(configFile));
5756
if (mInitialized)
5857
{
5958
ChipLogError(DeviceLayer, "ChipLinuxStorage::Init: Attempt to re-initialize with KVS config file: %s",
6059
StringOrNullMarker(configFile));
6160
return CHIP_NO_ERROR;
6261
}
6362

63+
ChipLogDetail(DeviceLayer, "ChipLinuxStorage::Init: Using KVS config file: %s", StringOrNullMarker(configFile));
64+
6465
mConfigPath.assign(configFile);
6566
retval = ChipLinuxStorageIni::Init();
6667

src/platform/Linux/CHIPLinuxStorageIni.cpp

+18-28
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <lib/support/CHIPMem.h>
3232
#include <lib/support/CodeUtils.h>
3333
#include <lib/support/IniEscaping.h>
34+
#include <lib/support/TemporaryFileStream.h>
3435
#include <lib/support/logging/CHIPLogging.h>
3536
#include <platform/Linux/CHIPLinuxStorageIni.h>
3637
#include <platform/internal/CHIPDeviceLayerInternal.h>
@@ -91,34 +92,23 @@ CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile)
9192
// 3. Using rename() to overwrite the existing file
9293
CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile)
9394
{
94-
CHIP_ERROR retval = CHIP_NO_ERROR;
95-
std::string tmpPath = configFile + "-XXXXXX";
96-
97-
int fd = mkstemp(&tmpPath[0]);
98-
if (fd != -1)
99-
{
100-
std::ofstream ofs;
101-
ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc);
102-
mConfigStore.generate(ofs);
103-
close(fd);
104-
105-
if (rename(tmpPath.c_str(), configFile.c_str()) == 0)
106-
{
107-
ChipLogDetail(DeviceLayer, "wrote settings to %s", configFile.c_str());
108-
}
109-
else
110-
{
111-
ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno);
112-
retval = CHIP_ERROR_WRITE_FAILED;
113-
}
114-
}
115-
else
116-
{
117-
ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str());
118-
retval = CHIP_ERROR_OPEN_FAILED;
119-
}
120-
121-
return retval;
95+
TemporaryFileStream tmpFile(configFile + "-XXXXXX");
96+
VerifyOrReturnError(
97+
tmpFile.IsOpen(), CHIP_ERROR_OPEN_FAILED,
98+
ChipLogError(DeviceLayer, "Failed to create temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno)));
99+
100+
mConfigStore.generate(tmpFile);
101+
VerifyOrReturnError(
102+
tmpFile.DataSync(), CHIP_ERROR_WRITE_FAILED,
103+
ChipLogError(DeviceLayer, "Failed to sync temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno)));
104+
105+
int rv = rename(tmpFile.GetFileName().c_str(), configFile.c_str());
106+
VerifyOrReturnError(rv == 0, CHIP_ERROR_WRITE_FAILED,
107+
ChipLogError(DeviceLayer, "Failed to rename %s to %s: %s", tmpFile.GetFileName().c_str(),
108+
configFile.c_str(), strerror(errno)));
109+
110+
ChipLogDetail(DeviceLayer, "Wrote settings to %s", configFile.c_str());
111+
return CHIP_NO_ERROR;
122112
}
123113

124114
CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val)

src/platform/NuttX/CHIPLinuxStorageIni.cpp

+18-32
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <lib/support/CHIPMem.h>
3232
#include <lib/support/CodeUtils.h>
3333
#include <lib/support/IniEscaping.h>
34+
#include <lib/support/TemporaryFileStream.h>
3435
#include <lib/support/logging/CHIPLogging.h>
3536
#include <platform/NuttX/CHIPLinuxStorageIni.h>
3637
#include <platform/internal/CHIPDeviceLayerInternal.h>
@@ -91,38 +92,23 @@ CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile)
9192
// 3. Using rename() to overwrite the existing file
9293
CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile)
9394
{
94-
CHIP_ERROR retval = CHIP_NO_ERROR;
95-
std::string tmpPath = configFile + "-XXXXXX";
96-
97-
int fd = mkstemp(&tmpPath[0]);
98-
if (fd != -1)
99-
{
100-
std::ofstream ofs;
101-
102-
ChipLogProgress(DeviceLayer, "writing settings to file (%s)", tmpPath.c_str());
103-
104-
ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc);
105-
mConfigStore.generate(ofs);
106-
107-
close(fd);
108-
109-
if (rename(tmpPath.c_str(), configFile.c_str()) == 0)
110-
{
111-
ChipLogProgress(DeviceLayer, "renamed tmp file to file (%s)", configFile.c_str());
112-
}
113-
else
114-
{
115-
ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno);
116-
retval = CHIP_ERROR_WRITE_FAILED;
117-
}
118-
}
119-
else
120-
{
121-
ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str());
122-
retval = CHIP_ERROR_OPEN_FAILED;
123-
}
124-
125-
return retval;
95+
TemporaryFileStream tmpFile(configFile + "-XXXXXX");
96+
VerifyOrReturnError(
97+
tmpFile.IsOpen(), CHIP_ERROR_OPEN_FAILED,
98+
ChipLogError(DeviceLayer, "Failed to create temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno)));
99+
100+
mConfigStore.generate(tmpFile);
101+
VerifyOrReturnError(
102+
tmpFile.DataSync(), CHIP_ERROR_WRITE_FAILED,
103+
ChipLogError(DeviceLayer, "Failed to sync temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno)));
104+
105+
int rv = rename(tmpFile.GetFileName().c_str(), configFile.c_str());
106+
VerifyOrReturnError(rv == 0, CHIP_ERROR_WRITE_FAILED,
107+
ChipLogError(DeviceLayer, "Failed to rename %s to %s: %s", tmpFile.GetFileName().c_str(),
108+
configFile.c_str(), strerror(errno)));
109+
110+
ChipLogDetail(DeviceLayer, "Wrote settings to %s", configFile.c_str());
111+
return CHIP_NO_ERROR;
126112
}
127113

128114
CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val)

src/platform/webos/CHIPWebOSStorageIni.cpp

+18-32
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <lib/support/Base64.h>
3131
#include <lib/support/CHIPMem.h>
3232
#include <lib/support/CodeUtils.h>
33+
#include <lib/support/TemporaryFileStream.h>
3334
#include <lib/support/logging/CHIPLogging.h>
3435
#include <platform/internal/CHIPDeviceLayerInternal.h>
3536
#include <platform/webos/CHIPWebOSStorageIni.h>
@@ -70,38 +71,23 @@ CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile)
7071
// 3. Using rename() to overwrite the existing file
7172
CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile)
7273
{
73-
CHIP_ERROR retval = CHIP_NO_ERROR;
74-
std::string tmpPath = configFile + "-XXXXXX";
75-
76-
int fd = mkstemp(&tmpPath[0]);
77-
if (fd != -1)
78-
{
79-
std::ofstream ofs;
80-
81-
ChipLogProgress(DeviceLayer, "writing settings to file (%s)", tmpPath.c_str());
82-
83-
ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc);
84-
mConfigStore.generate(ofs);
85-
86-
close(fd);
87-
88-
if (rename(tmpPath.c_str(), configFile.c_str()) == 0)
89-
{
90-
ChipLogProgress(DeviceLayer, "renamed tmp file to file (%s)", configFile.c_str());
91-
}
92-
else
93-
{
94-
ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno);
95-
retval = CHIP_ERROR_WRITE_FAILED;
96-
}
97-
}
98-
else
99-
{
100-
ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str());
101-
retval = CHIP_ERROR_OPEN_FAILED;
102-
}
103-
104-
return retval;
74+
TemporaryFileStream tmpFile(configFile + "-XXXXXX");
75+
VerifyOrReturnError(
76+
tmpFile.IsOpen(), CHIP_ERROR_OPEN_FAILED,
77+
ChipLogError(DeviceLayer, "Failed to create temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno)));
78+
79+
mConfigStore.generate(tmpFile);
80+
VerifyOrReturnError(
81+
tmpFile.DataSync(), CHIP_ERROR_WRITE_FAILED,
82+
ChipLogError(DeviceLayer, "Failed to sync temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno)));
83+
84+
int rv = rename(tmpFile.GetFileName().c_str(), configFile.c_str());
85+
VerifyOrReturnError(rv == 0, CHIP_ERROR_WRITE_FAILED,
86+
ChipLogError(DeviceLayer, "Failed to rename %s to %s: %s", tmpFile.GetFileName().c_str(),
87+
configFile.c_str(), strerror(errno)));
88+
89+
ChipLogDetail(DeviceLayer, "Wrote settings to %s", configFile.c_str());
90+
return CHIP_NO_ERROR;
10591
}
10692

10793
CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val)

0 commit comments

Comments
 (0)