View previous topic :: View next topic |
Author |
Message |
piii_man n00b
Joined: 18 Jul 2004 Posts: 39
|
Posted: Thu Apr 06, 2023 7:58 am Post subject: [SOLVED] Chown-ed / - How to recover? |
|
|
Hi all,
I really goofed and chowned my fs root.
Code: | sudo chown -R piii_man / |
I have backups, but of documents and etc files, not a system snapshot. A silver lining to the mistake was that I only chown-ed owner, not group. I was able to reverse most of the damage with a script that chowned files based on file stat info.
Code: | stat.st_ctime > self.ctime_cutoff and stat.st_gid != stat.st_uid and stat.st_uid == self.bad_uid |
Where bad_uid is the uid of user piii_man and ctime_cutoff is when I did the chown. The logic was, if the above is true chown the file to gid:gid.
This of course wasn't perfect (i.e. many files don't follow owner:group == group:group), but fixed a lot of the issues. After some other fixes, I have a pretty much working system where I can emerge things, run KDE etc. Looking through my journalctl, some things are definitely broken (FUSE for one). What's a more robust solution here? Can I just emerge -e @world, or will I have to tackle the remaining problems one by one? I would appreciate any insight you have.
Last edited by piii_man on Thu Apr 06, 2023 8:39 pm; edited 1 time in total |
|
Back to top |
|
|
alamahant Advocate
Joined: 23 Mar 2019 Posts: 3879
|
Posted: Thu Apr 06, 2023 8:37 am Post subject: |
|
|
Hi
Maybe
ie rebuild ALL packages.
If the above shows it possible then please remove the "p" flag.
But please take a second opinion also.
After doing that you might want to
Code: |
sudo chown -R $USER:$USER ~
|
And if you have other users also chown their home dirs to their names.
I am not sure what to do about
/var/lib/*
ie if you have docker or lxd containers or dbs or data in there.
Also consider your
/boot/*
/lib/modules/*
/usr/src/*
directories but I think you will be fine even with different owner.
If everything restores to its original state then please BACKUP everyday possibly before updating.
I always rsync my whole system to a partition before updating. _________________
|
|
Back to top |
|
|
Hu Administrator
Joined: 06 Mar 2007 Posts: 21793
|
Posted: Thu Apr 06, 2023 3:16 pm Post subject: |
|
|
It appears that the Portage vdb does not remember ownership. However, Portage binpkgs do remember ownership, so if you have been building tbz2 packages as you install things, you can reinstall from those to save yourself the time of a rebuild. With a bit of cleverness, you could even extract the permissions from the tbz2 and apply them without restoring the file contents. |
|
Back to top |
|
|
piii_man n00b
Joined: 18 Jul 2004 Posts: 39
|
Posted: Thu Apr 06, 2023 7:30 pm Post subject: |
|
|
Alright thanks guys. I just finished an emerge -e. After rebooting, journalctl -b0 looks good. I'll monitor it for any unusual logs. I don't want to bring bad luck, but this was actually less bad than I expected
@alamahant: I'm rethinking my backup strategy. I used to think configs and documents were what was most important, but I've started to value my time a lot more. I believe the script I wrote should cover most of the things you mentioned, but time will tell.
@Hu: That idea gets 10/10 for style points! Ultimately, this is a pretty new desktop so re-emerging the whole system isn't too bad.
Hopefully no one needs this, but for anyone who finds themselves in a similar position, here's the script I wrote. It is a hacky fix to file ownership just to give a mostly working system. It converts recently changed files with bad_uid:not_bad_gid to not_bad_gid:not_bad_gid. To use, modify the script to set set the ctime_cutoff to just before the errant chown and set the bad_uid to the uid you accidently chowned / with. You can run the script with sudo ./unChown.py / --dry-run. It will make a log file with all changes, in the same dir it's run in. If you like the changes, remove the --dry-run flag and re-run. On systems that are making use of lots of users in different groups, this script will make a lot of mistakes.
Code: |
#!/usr/bin/python3
import logging
import os
import sys
import time
from os.path import join
from typing import *
class unChown:
"""
This is a script to undo an errant chown of ONLY owner. The *imperfect* logic is that files and folders
where owner uid != group gid should be modified to owner = gid, group = gid. The script also looks at
file stat info and only changes files where the ctime is after a certain threshold (i.e. when the chown mistake
was made)
"""
def __init__(self, bad_uid: int, ctime_cutoff: int, log_path: str):
"""
*Note:* Only logs to file when used with a ```with``` statement.
:param bad_uid: owners to be undone if they don't owner != group
:param ctime_cutoff: no files with most recently change BEFORE this cutoff can be modified
:param log_path: path to start a recursive search
"""
self.bad_uid = bad_uid
self.ctime_cutoff = ctime_cutoff
self.log_path = log_path
# log file only created when called using a with statement. Is list of changes made (or to make if --dry-run)
self.log_file = None
self.logger = logging.getLogger(type(self).__name__)
def __enter__(self) -> 'unChown':
self.log_file = open(self.log_path, "a")
return self
def __exit__(self, exc_type, exc_value, traceback):
if self.log_file:
self.log_file.close()
self.log_file = None
def _shouldChown(self, stat: os.stat_result) -> bool:
"""
Decide if file or folder should be chowned.
:param stat: stat_result of the file/dir
:return: True if should be unChown-ed else False
"""
return stat.st_ctime >= self.ctime_cutoff and stat.st_gid != stat.st_uid and stat.st_uid == self.bad_uid
def _doChown(self, path: str, stat: os.stat_result, dry_run: bool) -> bool:
"""
Chown the file at ```path``` to owner = gid, group = gid
:param path: path
:param stat: stat_result for the path
:param dry_run: True don't actually modify file. False, do modify file.
:return: ```True``` if a chown was performed, else ```False```
"""
if not dry_run:
try:
os.chown(path=path, uid=stat.st_gid, gid=stat.st_gid, follow_symlinks=False)
except Exception as e:
self.logger.warning("Couldn't chown: " + str(e))
return False
return True
def _doLog(self, path: str, uid: int, gid: int) -> None:
"""
Log to std.out and if run using a with statement to a file
:param path: path to log
:param uid: uid of fs object at path
:param gid: gid of fs object at path
:return:
"""
log_line = f"{uid}:{gid} {path}"
self.logger.info(log_line)
if self.log_file:
self.log_file.write(f'{log_line}\n')
def _handlePath(self, path: str, dry_run=False) -> bool:
"""
:param path: path to filesystem object
:param dry_run:
:return:
"""
try:
stat = os.lstat(path)
except Exception as e:
self.logger.warning(f"Unable to stat: {path}: {str(e)}")
return False
if self._shouldChown(stat) and self._doChown(path=path, stat=stat, dry_run=dry_run):
self._doLog(path=path, uid=stat.st_uid, gid=stat.st_gid)
return True
return False
def run(self, root_path: str, dry_run: bool = False) -> None:
"""
:param root_path: path to start filesystem walk from
:param dry_run: if ```True``` no changes will be made to file permissions, a log may be created *see:*
constructor for more info
:return:
"""
for dir_path, dir_names, file_names in os.walk(top=root_path, topdown=True, followlinks=False):
self._handlePath(path=dir_path, dry_run=dry_run)
for file_name in file_names:
file_path = join(dir_path, file_name)
self._handlePath(path=file_path, dry_run=dry_run)
# [ROOT_DIR] {optional: --dry-run}
def parseArgs() -> List[str | bool]:
args = sys.argv[1:]
if len(args) == 1:
args.append(False)
elif len(args) == 2 and args[1].lower() == "--dry-run": # dry-run == no changes to disk
args[1] = True
else:
raise ValueError("expected at least 1 arg: {root path} and the optional --dry-run flag")
return args
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
root_path, dry_run = parseArgs() # location to start walk form
log_path = f"log-{int(round(time.time(), 0))}" # path to write log file
ctime_cutoff = 1680734730 - 30 # only files/dirs with ctimes AT or AFTER this will be modified
bad_uid = 1_000 # owner uid which should be changed
with unChown(bad_uid=bad_uid, ctime_cutoff=ctime_cutoff, log_path=log_path) as chown:
chown.run(root_path=root_path, dry_run=dry_run)
|
And here are some 1 off changes I made. Everything should be done from a Live USB.
Code: |
chmod +s su
chmod +s sudo
sudo chown polkitd:root /usr/lib/polkit-1/polkitd
sudo chmod 5755 /usr/lib/polkit-1/polkitd
sudo chown -R polkitd:root /etc/polkit-1
|
|
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|