diff options
Diffstat (limited to 'src/envsecurity.cpp')
| -rw-r--r-- | src/envsecurity.cpp | 472 |
1 files changed, 412 insertions, 60 deletions
diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 376be4df..ffb17c42 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -9,6 +9,11 @@ #include <netfw.h> #pragma comment(lib, "Wbemuuid.lib") +#include <accctrl.h> +#include <aclapi.h> +#include <sddl.h> +#pragma comment(lib, "advapi32.lib") + namespace env { @@ -156,6 +161,11 @@ SecurityProduct::SecurityProduct( { } +const QUuid& SecurityProduct::guid() const +{ + return m_guid; +} + const QString& SecurityProduct::name() const { return m_name; @@ -180,7 +190,13 @@ QString SecurityProduct::toString() const { QString s; - s += m_name + " (" + providerToString() + ")"; + if (m_name.isEmpty()) { + s += "(no name)"; + } else { + s += m_name; + } + + s += " (" + providerToString() + ")"; if (!m_active) { s += ", inactive"; @@ -190,7 +206,9 @@ QString SecurityProduct::toString() const s += ", definitions outdated"; } - if (!m_guid.isNull()) { + if (m_guid.isNull()) { + s += ", (no guid)"; + } else { s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); } @@ -237,91 +255,98 @@ QString SecurityProduct::providerToString() const } -std::vector<SecurityProduct> getSecurityProductsFromWMI() +std::optional<SecurityProduct> handleProduct(IWbemClassObject* o) { - // some products may be present in multiple queries, such as a product marked - // as both antivirus and antispyware, but they'll have the same GUID, so use - // that to avoid duplicating entries - std::map<QUuid, SecurityProduct> map; + VARIANT prop; - auto handleProduct = [&](auto* o) { - VARIANT prop; - // display name - auto ret = o->Get(L"displayName", 0, &prop, 0, 0); - if (FAILED(ret)) { - log::error("failed to get displayName, {}", formatSystemMessage(ret)); - return; - } + // guid + auto ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); + return {}; + } - if (prop.vt != VT_BSTR) { - log::error("displayName is a {}, not a bstr", prop.vt); - return; - } + if (prop.vt != VT_BSTR) { + log::error("instanceGuid is a {}, not a bstr", prop.vt); + return {}; + } - const std::wstring name = prop.bstrVal; - VariantClear(&prop); + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); - // product state - ret = o->Get(L"productState", 0, &prop, 0, 0); - if (FAILED(ret)) { - log::error("failed to get productState, {}", formatSystemMessage(ret)); - return; - } - if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - log::error("productState is a {}, is not a VT_UI4", prop.vt); - return; - } + // display name + QString displayName; + ret = o->Get(L"displayName", 0, &prop, 0, 0); - DWORD state = 0; + if (FAILED(ret)) { + log::error("failed to get displayName, {}", formatSystemMessage(ret)); + } else if (prop.vt != VT_BSTR) { + log::error("displayName is a {}, not a bstr", prop.vt); + } else { + displayName = QString::fromWCharArray(prop.bstrVal); + } + + VariantClear(&prop); + + + // product state + DWORD state = 0; + ret = o->Get(L"productState", 0, &prop, 0, 0); + + if (FAILED(ret)) { + log::error("failed to get productState, {}", formatSystemMessage(ret)); + } else { if (prop.vt == VT_I4) { state = prop.lVal; - } else { + } else if (prop.vt == VT_UI4) { state = prop.ulVal; + } else if (prop.vt == VT_NULL) { + log::warn("productState is null"); + } else { + log::error("productState is a {}, not a VT_I4 or a VT_UI4", prop.vt); } + } - VariantClear(&prop); + VariantClear(&prop); - // guid - ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); - if (FAILED(ret)) { - log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); - return; - } - if (prop.vt != VT_BSTR) { - log::error("instanceGuid is a {}, is not a bstr", prop.vt); - return; - } + const auto provider = static_cast<int>((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; - const QUuid guid(QString::fromWCharArray(prop.bstrVal)); - VariantClear(&prop); + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); - const auto provider = static_cast<int>((state >> 16) & 0xff); - const auto scanner = (state >> 8) & 0xff; - const auto definitions = state & 0xff; + return SecurityProduct(guid, displayName, provider, active, upToDate); +} - const bool active = ((scanner & 0x10) != 0); - const bool upToDate = (definitions == 0); +std::vector<SecurityProduct> getSecurityProductsFromWMI() +{ + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map<QUuid, SecurityProduct> map; - map.insert({ - guid, - {guid, QString::fromStdWString(name), provider, active, upToDate}}); + auto f = [&](auto* o) { + if (auto p=handleProduct(o)) { + map.emplace(p->guid(), std::move(*p)); + } }; { WMI wmi("root\\SecurityCenter2"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); + wmi.query("select * from AntivirusProduct", f); + wmi.query("select * from FirewallProduct", f); + wmi.query("select * from AntiSpywareProduct", f); } { WMI wmi("root\\SecurityCenter"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); + wmi.query("select * from AntivirusProduct", f); + wmi.query("select * from FirewallProduct", f); + wmi.query("select * from AntiSpywareProduct", f); } std::vector<SecurityProduct> v; @@ -397,4 +422,331 @@ std::vector<SecurityProduct> getSecurityProducts() return v; } + +class failed +{ +public: + failed(DWORD e, QString what) + : m_what(what + ", " + QString::fromStdWString(formatSystemMessage(e))) + { + } + + QString what() const + { + return m_what; + } + +private: + QString m_what; +}; + + +MallocPtr<SECURITY_DESCRIPTOR> getSecurityDescriptor(const QString& path) +{ + const auto wpath = path.toStdWString(); + BOOL ret = FALSE; + + DWORD length = 0; + ret = ::GetFileSecurityW( + wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, + nullptr, 0, &length); + + if (!ret || length == 0) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + if (e == ERROR_ACCESS_DENIED) { + // if this fails, the user doesn't even have permissions to get the + // security descriptor, which probably means they're not the owner and + // their effective access is none + throw failed(e, "cannot get security descriptor"); + } else { + // other error + throw failed(e, "GetFileSecurity() for length failed"); + } + } + } + + MallocPtr<SECURITY_DESCRIPTOR> sd( + static_cast<SECURITY_DESCRIPTOR*>(std::malloc(length))); + + std::memset(sd.get(), 0, length); + + ret = ::GetFileSecurityW( + wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, + sd.get(), length, &length); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetFileSecurity()"); + } + + return sd; +} + +PACL getDacl(SECURITY_DESCRIPTOR* sd) +{ + BOOL present = FALSE; + BOOL daclDefaulted = FALSE; + PACL acl = nullptr; + + BOOL ret = ::GetSecurityDescriptorDacl(sd, &present, &acl, &daclDefaulted); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetSecurityDescriptorDacl()"); + } + + if (!present) { + return nullptr; + } + + return acl; +} + +PSID getFileOwner(SECURITY_DESCRIPTOR* sd) +{ + BOOL ownerDefaulted = FALSE; + PSID owner; + + BOOL ret = ::GetSecurityDescriptorOwner(sd, &owner, &ownerDefaulted); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetSecurityDescriptionOwner()"); + } + + return owner; +} + +MallocPtr<void> getCurrentUser() +{ + HANDLE hnd = ::GetCurrentProcess(); + HANDLE rawToken = 0; + + BOOL ret = ::OpenProcessToken(hnd, TOKEN_QUERY, &rawToken); + if (!ret) { + const auto e = GetLastError(); + throw(e, "OpenProcessToken()"); + } + + HandlePtr token(rawToken); + + DWORD retsize = 0; + ret = ::GetTokenInformation(token.get(), TokenUser, 0, 0, &retsize); + + if (!ret) { + const auto e = GetLastError(); + if (e != ERROR_INSUFFICIENT_BUFFER) { + throw failed(e, "GetTokenInformation() for length"); + } + } + + MallocPtr<void> tokenBuffer(std::malloc(retsize)); + ret = ::GetTokenInformation( + token.get(), TokenUser, tokenBuffer.get(), retsize, &retsize); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetTokenInformation()"); + } + + PSID tokenSid = ((PTOKEN_USER)(tokenBuffer.get()))->User.Sid; + DWORD sidLen = ::GetLengthSid(tokenSid); + MallocPtr<void> currentUserSID((SID*)(malloc(sidLen))); + + ret = ::CopySid(sidLen, currentUserSID.get(), tokenSid); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "CopySid()"); + } + + return currentUserSID; +} + +ACCESS_MASK getEffectiveRights(ACL* dacl, PSID sid) +{ + TRUSTEEW trustee = {}; + BuildTrusteeWithSid(&trustee, sid); + + ACCESS_MASK access = 0; + DWORD ret = ::GetEffectiveRightsFromAclW(dacl, &trustee, &access); + + if (ret != ERROR_SUCCESS) { + throw failed(ret, "GetEffectiveRightsFromAclW()"); + } + + return access; +} + +QString getUsername(PSID owner) +{ + DWORD nameSize=0, domainSize=0; + auto use = SidTypeUnknown; + + BOOL ret = LookupAccountSidW( + nullptr, owner, nullptr, &nameSize, nullptr, &domainSize, &use); + + if (!ret) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + throw failed(e, "LookupAccountSid() for sizes"); + } + } + + auto wsName = std::make_unique<wchar_t[]>(nameSize); + auto wsDomain = std::make_unique<wchar_t[]>(domainSize); + + ret = LookupAccountSidW( + nullptr, owner, wsName.get(), &nameSize, wsDomain.get(), &domainSize, &use); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "LookupAccountSid()"); + } + + const QString name = QString::fromWCharArray(wsName.get(), nameSize); + const QString domain = QString::fromWCharArray(wsDomain.get(), domainSize); + + if (!name.isEmpty() && !domain.isEmpty()) { + return domain + "\\" + name; + } else { + // either or both are empty + return name + domain; + } +} + +FileRights makeFileRights(ACCESS_MASK m) +{ + FileRights fr; + + if (m & FILE_GENERIC_READ) { + fr.list.push_back("file_generic_read"); + } else { + if (m & READ_CONTROL) { + fr.list.push_back("read_ctrl"); + } + + if (m & FILE_READ_DATA) { + fr.list.push_back("read_data"); + } + + if (m & FILE_READ_ATTRIBUTES) { + fr.list.push_back("read_atts"); + } + + if (m & FILE_READ_EA) { + fr.list.push_back("read_ex_atts"); + } + + if (m & SYNCHRONIZE) { + fr.list.push_back("sync"); + } + } + + if (m & FILE_GENERIC_WRITE) { + fr.list.push_back("file_generic_write"); + } else { + // READ_CONTROL handled above + + if (m & FILE_WRITE_DATA) { + fr.list.push_back("write_data"); + } + + if (m & FILE_WRITE_ATTRIBUTES) { + fr.list.push_back("write_atts"); + } + + if (m & FILE_WRITE_EA) { + fr.list.push_back("write_ex_atts"); + } + + if (m & FILE_APPEND_DATA) { + fr.list.push_back("append_data"); + } + + // SYNCHRONIZE handled above + } + + if (m & FILE_GENERIC_EXECUTE) { + fr.list.push_back("file_generic_execute"); + fr.hasExecute = true; + } else { + // READ_CONTROL handled above + // FILE_READ_ATTRIBUTES handled above + + if (m & FILE_EXECUTE) { + fr.list.push_back("execute"); + fr.hasExecute = true; + } + + // SYNCHRONIZE handled above + } + + if (m & DELETE) { + fr.list.push_back("delete"); + } + + if (m & WRITE_DAC) { + fr.list.push_back("write_dac"); + } + + if (m & WRITE_OWNER) { + fr.list.push_back("write_owner"); + } + + if (m & GENERIC_ALL) { + fr.list.push_back("generic_all"); + } + + if (m & GENERIC_WRITE) { + fr.list.push_back("generic_write"); + } + + if (m & GENERIC_READ) { + fr.list.push_back("generic_read"); + } + + // 0x001f01ff + const auto normalRights = + STANDARD_RIGHTS_ALL | + FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | + FILE_DELETE_CHILD; + + if (m == normalRights) { + fr.normalRights = true; + } + + return fr; +} + +FileSecurity getFileSecurity(const QString& path) +{ + FileSecurity fs; + + try + { + auto sd = getSecurityDescriptor(path); + auto dacl = getDacl(sd.get()); + auto currentUser = getCurrentUser(); + auto owner = getFileOwner(sd.get()); + auto access = getEffectiveRights(dacl, currentUser.get()); + + fs.rights = makeFileRights(access); + + if (EqualSid(owner, currentUser.get())) { + fs.owner = "(this user)"; + } else { + fs.owner = getUsername(owner); + } + } + catch(failed& f) + { + fs.error = f.what(); + } + + return fs; +} } // namespace |
