1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-28 03:37:35 +00:00

passwd+LibCore: Make passwd replace /etc files atomically

Before this patch, we had a nasty race condition when changing a user's
password: there was a time window between truncating /etc/shadow and
writing out its new contents, where you could simply "su" to root
without using a password.

Instead of writing directly to /etc/passwd and /etc/shadow, we now
create temporary files in /etc and fill them with the new contents.
Those files are then atomically renamed to /etc/passwd and /etc/shadow.

Sadly, fixing this race requires giving the passwd program a lot more
privileges. This is something we can and should improve upon. :^)
This commit is contained in:
Andreas Kling 2021-01-21 09:58:31 +01:00
parent c9a7f81dc3
commit 77e0598c6d
2 changed files with 54 additions and 52 deletions

View file

@ -26,12 +26,14 @@
#include <AK/Base64.h>
#include <AK/Random.h>
#include <AK/ScopeGuard.h>
#include <LibCore/Account.h>
#include <LibCore/File.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
namespace Core {
@ -273,39 +275,57 @@ bool Account::sync()
ASSERT(m_shadow_file);
ASSERT(m_shadow_file->mode() == Core::File::OpenMode::ReadWrite);
// FIXME: Maybe reorganize this to create temporary files and finish it completely before renaming them to /etc/{passwd,shadow}
// If truncation succeeds but write fails, we'll have an empty file :(
auto new_passwd_file_content = generate_passwd_file();
auto new_shadow_file_content = generate_shadow_file();
auto new_passwd_file = generate_passwd_file();
auto new_shadow_file = generate_shadow_file();
if (new_passwd_file.is_null() || new_shadow_file.is_null()) {
if (new_passwd_file_content.is_null() || new_shadow_file_content.is_null()) {
ASSERT_NOT_REACHED();
}
if (!m_passwd_file->seek(0) || !m_shadow_file->seek(0)) {
ASSERT_NOT_REACHED();
char new_passwd_name[] = "/etc/passwd.XXXXXX";
char new_shadow_name[] = "/etc/shadow.XXXXXX";
{
auto new_passwd_fd = mkstemp(new_passwd_name);
if (new_passwd_fd < 0) {
perror("mkstemp");
ASSERT_NOT_REACHED();
}
ScopeGuard new_passwd_fd_guard = [new_passwd_fd] { close(new_passwd_fd); };
auto new_shadow_fd = mkstemp(new_shadow_name);
if (new_shadow_fd < 0) {
perror("mkstemp");
ASSERT_NOT_REACHED();
}
ScopeGuard new_shadow_fd_guard = [new_shadow_fd] { close(new_shadow_fd); };
if (fchmod(new_passwd_fd, 0644) < 0) {
perror("fchmod");
ASSERT_NOT_REACHED();
}
auto nwritten = write(new_passwd_fd, new_passwd_file_content.characters(), new_passwd_file_content.length());
if (nwritten < 0) {
perror("write");
ASSERT_NOT_REACHED();
}
ASSERT(static_cast<size_t>(nwritten) == new_passwd_file_content.length());
nwritten = write(new_shadow_fd, new_shadow_file_content.characters(), new_shadow_file_content.length());
if (nwritten < 0) {
perror("write");
ASSERT_NOT_REACHED();
}
ASSERT(static_cast<size_t>(nwritten) == new_shadow_file_content.length());
}
if (!m_passwd_file->truncate(0)) {
dbgln("Truncating passwd file failed.");
if (rename(new_passwd_name, "/etc/passwd") < 0) {
perror("Failed to install new /etc/passwd");
return false;
}
if (!m_passwd_file->write(new_passwd_file)) {
// FIXME: Improve Core::File::write() error reporting.
dbgln("Writing to passwd file failed.");
return false;
}
if (!m_shadow_file->truncate(0)) {
dbgln("Truncating shadow file failed.");
return false;
}
if (!m_shadow_file->write(new_shadow_file)) {
// FIXME: Improve Core::File::write() error reporting.
dbgln("Writing to shadow file failed.");
if (rename(new_shadow_name, "/etc/shadow") < 0) {
perror("Failed to install new /etc/shadow");
return false;
}