summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
authorDan McDonald <danmcd@mnx.io>2022-10-04 10:23:07 -0400
committerDan McDonald <danmcd@mnx.io>2022-10-04 10:23:07 -0400
commit06ed4b8d465c6c863197ec44ea3bf24587de157c (patch)
tree710b9d1784f40ac8a85c91072063b3ce6186fb1f /usr/src
parent80d7725bf1e4fb33051ad152f649814a6aea8d62 (diff)
parent37e2cd25d56b334a2403f2540a0b0a1e6a40bcd1 (diff)
downloadillumos-joyent-06ed4b8d465c6c863197ec44ea3bf24587de157c.tar.gz
[illumos-gate merge]
commit 37e2cd25d56b334a2403f2540a0b0a1e6a40bcd1 15031 event ports impacted by FDINFO accesses commit 6a23a4b8ef29200f409ef8273bc56cdc0ec24e0a 15029 AD join observability
Diffstat (limited to 'usr/src')
-rw-r--r--usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c34
-rw-r--r--usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c143
-rw-r--r--usr/src/pkg/manifests/system-test-ostest.p5m2
-rw-r--r--usr/src/test/os-tests/runfiles/default.run3
-rw-r--r--usr/src/test/os-tests/tests/Makefile1
-rw-r--r--usr/src/test/os-tests/tests/regression/Makefile53
-rw-r--r--usr/src/test/os-tests/tests/regression/illumos-15031.c85
-rw-r--r--usr/src/uts/common/fs/proc/prdata.h1
-rw-r--r--usr/src/uts/common/fs/proc/prsubr.c49
-rw-r--r--usr/src/uts/common/fs/proc/prvnops.c10
10 files changed, 342 insertions, 39 deletions
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c
index 942650545f..9191cfbce6 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c
+++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c
@@ -21,6 +21,7 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2018 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2021 RackTop Systems, Inc.
*/
/*
@@ -51,11 +52,11 @@
static DWORD
mlsvc_join_rpc(smb_domainex_t *dxi,
- char *admin_user, char *admin_pw,
- char *machine_name, char *machine_pw);
+ char *admin_user, char *admin_pw,
+ char *machine_name, char *machine_pw);
static DWORD
mlsvc_join_noauth(smb_domainex_t *dxi,
- char *machine_name, char *machine_pw);
+ char *machine_name, char *machine_pw);
/*
* This is called by smbd_dc_update just after we've learned about a
@@ -96,6 +97,7 @@ mlsvc_join(smb_joininfo_t *info, smb_joinres_t *res)
char machine_name[SMB_SAMACCT_MAXLEN];
char machine_pw[NETR_MACHINE_ACCT_PASSWD_MAX];
unsigned char passwd_hash[SMBAUTH_HASH_SZ];
+ char addrbuf[INET6_ADDRSTRLEN];
smb_domainex_t dxi;
smb_domain_t *di = &dxi.d_primary;
DWORD status;
@@ -126,8 +128,11 @@ mlsvc_join(smb_joininfo_t *info, smb_joinres_t *res)
if (info->domain_username[0] != '\0') {
(void) smb_auth_ntlm_hash(info->domain_passwd, passwd_hash);
smb_ipc_set(info->domain_username, passwd_hash);
+ syslog(LOG_INFO, "smbd: joining with user %s",
+ info->domain_username);
} else {
smb_ipc_set(MLSVC_ANON_USER, zero_hash);
+ syslog(LOG_INFO, "smbd: joining with anonymous");
}
/*
@@ -139,6 +144,7 @@ mlsvc_join(smb_joininfo_t *info, smb_joinres_t *res)
syslog(LOG_NOTICE, "Failed to set idmap domain name");
if (smb_config_refresh_idmap() != 0)
syslog(LOG_NOTICE, "Failed to refresh idmap service");
+ syslog(LOG_INFO, "smbd: set idmap domain %s", info->domain_name);
/* Clear DNS local (ADS) lookup cache. */
smb_ads_refresh(B_FALSE);
@@ -161,7 +167,11 @@ mlsvc_join(smb_joininfo_t *info, smb_joinres_t *res)
* so that admin will know which AD server we were talking to.
*/
(void) strlcpy(res->dc_name, dxi.d_dci.dc_name, MAXHOSTNAMELEN);
- syslog(LOG_INFO, "smbd: found AD server %s", dxi.d_dci.dc_name);
+ if (smb_inet_ntop(&dxi.d_dci.dc_addr,
+ addrbuf, sizeof (addrbuf)) == NULL)
+ strcpy(addrbuf, "?");
+ syslog(LOG_INFO, "smbd: found AD server %s (%s)",
+ dxi.d_dci.dc_name, addrbuf);
/*
* Domain discovery needs to authenticate with the AD server.
@@ -190,6 +200,13 @@ mlsvc_join(smb_joininfo_t *info, smb_joinres_t *res)
*/
/*
+ * Log info about the domain from ddiscover
+ */
+ syslog(LOG_INFO, "smbd_join: domain FQN=%s", di->di_fqname);
+ syslog(LOG_INFO, "smbd_join: domain NBN=%s", di->di_nbname);
+ syslog(LOG_INFO, "smbd_join: domain SID=%s", di->di_sid);
+
+ /*
* Create or update our machine account on the DC.
* A non-null user means we do "secure join".
*/
@@ -199,6 +216,7 @@ mlsvc_join(smb_joininfo_t *info, smb_joinres_t *res)
*/
status = NT_STATUS_UNSUCCESSFUL;
if (ads_enabled) {
+ syslog(LOG_INFO, "use_ads=true (LDAP join)");
res->join_err = smb_ads_join(di->di_fqname,
info->domain_username, info->domain_passwd,
machine_pw);
@@ -206,7 +224,7 @@ mlsvc_join(smb_joininfo_t *info, smb_joinres_t *res)
status = NT_STATUS_SUCCESS;
}
} else {
- syslog(LOG_DEBUG, "use_ads=false (do RPC join)");
+ syslog(LOG_INFO, "use_ads=false (RPC join)");
/*
* If ADS was disabled, join using RPC.
@@ -294,8 +312,8 @@ out:
static DWORD
mlsvc_join_rpc(smb_domainex_t *dxi,
- char *admin_user, char *admin_pw,
- char *machine_name, char *machine_pw)
+ char *admin_user, char *admin_pw,
+ char *machine_name, char *machine_pw)
{
mlsvc_handle_t samr_handle;
mlsvc_handle_t domain_handle;
@@ -388,7 +406,7 @@ out_samr_handle:
*/
static DWORD
mlsvc_join_noauth(smb_domainex_t *dxi,
- char *machine_name, char *machine_pw)
+ char *machine_name, char *machine_pw)
{
char old_pw[SMB_SAMACCT_MAXLEN];
DWORD status;
diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c
index 44ae747bbf..ca2897aea1 100644
--- a/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c
+++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c
@@ -21,6 +21,7 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2019 Nexenta by DDN, Inc. All rights reserved.
+ * Copyright 2021 RackTop Systems, Inc.
*/
#include <sys/param.h>
@@ -422,7 +423,7 @@ again:
NULL, /* ComputerName */
domain,
NULL, /* DomainGuid */
- NULL, /* SiteName */
+ NULL, /* SiteName */
flags,
&dci);
switch (status) {
@@ -653,8 +654,10 @@ smb_ads_open_main(smb_ads_handle_t **hp, char *domain, char *user,
if (user != NULL) {
err = smb_kinit(domain, user, password);
- if (err != 0)
+ if (err != 0) {
+ syslog(LOG_ERR, "smbns: kinit failed");
return (err);
+ }
user = NULL;
password = NULL;
}
@@ -729,6 +732,14 @@ smb_ads_open_main(smb_ads_handle_t **hp, char *domain, char *user,
}
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
+ syslog(LOG_DEBUG, "smbns: smb_ads_open_main");
+ syslog(LOG_DEBUG, "smbns: domain: %s", ah->domain);
+ syslog(LOG_DEBUG, "smbns: domain_dn: %s", ah->domain_dn);
+ syslog(LOG_DEBUG, "smbns: ip_addr: %s", ah->ip_addr);
+ syslog(LOG_DEBUG, "smbns: hostname: %s", ah->hostname);
+ syslog(LOG_DEBUG, "smbns: site: %s",
+ (ah->site != NULL) ? ah->site : "");
+
rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL,
LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL);
if (rc != LDAP_SUCCESS) {
@@ -738,6 +749,7 @@ smb_ads_open_main(smb_ads_handle_t **hp, char *domain, char *user,
free(ads_host);
return (SMB_ADS_LDAP_SASL_BIND);
}
+ syslog(LOG_DEBUG, "smbns: ldap_sasl_..._bind_s success");
free(ads_host);
*hp = ah;
@@ -1238,13 +1250,15 @@ smb_ads_get_dc_level(smb_ads_handle_t *ah)
LDAPMessage *res, *entry;
char *attr[2];
char **vals;
- int rc = -1;
+ int rc;
res = NULL;
attr[0] = SMB_ADS_ATTR_DCLEVEL;
attr[1] = NULL;
- if (ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr,
- 0, &res) != LDAP_SUCCESS) {
+ rc = ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr, 0, &res);
+ if (rc != LDAP_SUCCESS) {
+ syslog(LOG_ERR, "smb_ads_get_dc_level: "
+ "LDAP search, error %s", ldap_err2string(rc));
(void) ldap_msgfree(res);
return (-1);
}
@@ -1255,6 +1269,7 @@ smb_ads_get_dc_level(smb_ads_handle_t *ah)
return (-1);
}
+ rc = -1;
entry = ldap_first_entry(ah->ld, res);
if (entry) {
if ((vals = ldap_get_values(ah->ld, entry,
@@ -1263,13 +1278,17 @@ smb_ads_get_dc_level(smb_ads_handle_t *ah)
* Observed the values aren't populated
* by the Windows 2000 server.
*/
+ syslog(LOG_DEBUG, "smb_ads_get_dc_level: "
+ "LDAP values missing, assume W2K");
(void) ldap_msgfree(res);
return (SMB_ADS_DCLEVEL_W2K);
}
- if (vals[0] != NULL)
+ if (vals[0] != NULL) {
rc = atoi(vals[0]);
-
+ syslog(LOG_DEBUG, "smb_ads_get_dc_level: "
+ "LDAP value %d", rc);
+ }
ldap_value_free(vals);
}
@@ -1310,6 +1329,9 @@ smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn)
int max;
smb_krb5_pn_set_t spn, upn;
+ syslog(LOG_DEBUG, "smb_ads_computer_op, op=%s dn=%s",
+ (op == LDAP_MOD_ADD) ? "add" : "replace", dn);
+
if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
return (-1);
@@ -1437,25 +1459,42 @@ smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair)
assert(avpair);
avpair->avp_val = NULL;
+
+ syslog(LOG_DEBUG, "smbns: ads_getattr (%s)", avpair->avp_attr);
vals = ldap_get_values(ld, entry, avpair->avp_attr);
- if (!vals)
+ if (!vals) {
+ syslog(LOG_DEBUG, "smbns: ads_getattr err: no vals");
return (SMB_ADS_STAT_NOT_FOUND);
-
+ }
if (!vals[0]) {
+ syslog(LOG_DEBUG, "smbns: ads_getattr err: no vals[0]");
ldap_value_free(vals);
return (SMB_ADS_STAT_NOT_FOUND);
}
avpair->avp_val = strdup(vals[0]);
- if (!avpair->avp_val)
+ if (!avpair->avp_val) {
+ syslog(LOG_DEBUG, "smbns: ads_getattr err: no mem");
rc = SMB_ADS_STAT_ERR;
+ } else {
+ syslog(LOG_DEBUG, "smbns: ads_getattr (%s) OK, val=%s",
+ avpair->avp_attr, avpair->avp_val);
+ }
ldap_value_free(vals);
return (rc);
}
/*
- * Process query's result.
+ * Process query's result, making sure we have what we need.
+ *
+ * There's some non-obvious logic here for checking the returned
+ * DNS name for the machine account, trying to avoid modifying
+ * someone else's machine account. When we search for a machine
+ * account we always ask for the DNS name. For a pre-created
+ * machine account, the DNS name will be not set, and that's OK.
+ * If we see a DNS name and it doesn't match our DNS name, we'll
+ * assume the account belongs to someone else and return "DUP".
*/
static smb_ads_qstat_t
smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
@@ -1469,37 +1508,50 @@ smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
return (SMB_ADS_STAT_ERR);
- if (ldap_count_entries(ah->ld, res) == 0)
+ if (ldap_count_entries(ah->ld, res) == 0) {
+ syslog(LOG_DEBUG, "smbns: find_computer, "
+ "ldap_count_entries zero");
return (SMB_ADS_STAT_NOT_FOUND);
+ }
- if ((entry = ldap_first_entry(ah->ld, res)) == NULL)
+ if ((entry = ldap_first_entry(ah->ld, res)) == NULL) {
+ syslog(LOG_DEBUG, "smbns: find_computer, "
+ "ldap_first_entry error");
return (SMB_ADS_STAT_ERR);
+ }
+ syslog(LOG_DEBUG, "smbns: find_computer, check DNS name");
dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST;
rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp);
+ /*
+ * Check the status of finding the DNS name
+ */
switch (rc) {
case SMB_ADS_STAT_FOUND:
/*
- * Returns SMB_ADS_STAT_DUP to avoid overwriting
+ * Found a DNS name. If it doesn't match ours,
+ * returns SMB_ADS_STAT_DUP to avoid overwriting
* the computer account of another system whose
* NetBIOS name collides with that of the current
* system.
*/
- if (strcasecmp(dnshost_avp.avp_val, fqhost))
+ if (strcasecmp(dnshost_avp.avp_val, fqhost)) {
+ syslog(LOG_DEBUG, "smbns: find_computer, "
+ "duplicate name (%s)",
+ dnshost_avp.avp_val);
rc = SMB_ADS_STAT_DUP;
-
+ }
free(dnshost_avp.avp_val);
break;
case SMB_ADS_STAT_NOT_FOUND:
/*
- * Pre-created computer account doesn't have
- * the dNSHostname attribute. It's been observed
- * that the dNSHostname attribute is only set after
- * a successful domain join.
- * Returns SMB_ADS_STAT_FOUND as the account is
- * pre-created for the current system.
+ * No dNSHostname attribute, so probably a
+ * pre-created computer account. Use it.
+ *
+ * Returns SMB_ADS_STAT_FOUND for the status
+ * of finding the machine account.
*/
rc = SMB_ADS_STAT_FOUND;
break;
@@ -1511,11 +1563,13 @@ smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
if (rc != SMB_ADS_STAT_FOUND)
return (rc);
- if (avpair)
+ if (avpair) {
+ syslog(LOG_DEBUG, "smbns: find_computer, check %s",
+ avpair->avp_attr);
rc = smb_ads_getattr(ah->ld, entry, avpair);
+ }
return (rc);
-
}
/*
@@ -1524,7 +1578,9 @@ smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
* If avpair is NULL, checks the status of the specified computer account.
* Otherwise, looks up the value of the specified computer account's attribute.
* If found, the value field of the avpair will be allocated and set. The
- * caller should free the allocated buffer.
+ * caller should free the allocated buffer. Caller avpair requests are:
+ * smb_ads_find_computer() asks for SMB_ADS_ATTR_DN
+ * smb_ads_lookup_computer_attr_kvno() SMB_ADS_ATTR_KVNO
*
* Return:
* SMB_ADS_STAT_FOUND - if both the computer and the specified attribute is
@@ -1544,6 +1600,7 @@ smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair,
LDAPMessage *res;
char sam_acct[SMB_SAMACCT_MAXLEN], sam_acct2[SMB_SAMACCT_MAXLEN];
smb_ads_qstat_t rc;
+ int err;
if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
return (SMB_ADS_STAT_ERR);
@@ -1567,15 +1624,37 @@ smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair,
"(&(objectClass=computer)(%s=%s))", SMB_ADS_ATTR_SAMACCT,
sam_acct2);
- if (ldap_search_s(ah->ld, dn, scope, filter, attrs, 0,
- &res) != LDAP_SUCCESS) {
+ syslog(LOG_DEBUG, "smbns: lookup_computer, "
+ "dn=%s, scope=%d", dn, scope);
+ syslog(LOG_DEBUG, "smbns: lookup_computer, "
+ "filter=%s", filter);
+ syslog(LOG_DEBUG, "smbns: lookup_computer, "
+ "attrs[0]=%s", attrs[0]);
+ syslog(LOG_DEBUG, "smbns: lookup_computer, "
+ "attrs[1]=%s", attrs[1] ? attrs[1] : "");
+
+ err = ldap_search_s(ah->ld, dn, scope, filter, attrs, 0, &res);
+ if (err != LDAP_SUCCESS) {
+ syslog(LOG_DEBUG, "smbns: lookup_computer, "
+ "LDAP search failed, dn=(%s), scope=%d, err=%s",
+ dn, scope, ldap_err2string(err));
(void) ldap_msgfree(res);
return (SMB_ADS_STAT_NOT_FOUND);
}
+ syslog(LOG_DEBUG, "smbns: find_computer, ldap_search OK");
rc = smb_ads_get_qstat(ah, res, avpair);
+ if (rc == SMB_ADS_STAT_FOUND) {
+ syslog(LOG_DEBUG, "smbns: find_computer, attr %s = %s",
+ avpair->avp_attr, avpair->avp_val);
+ } else {
+ syslog(LOG_DEBUG, "smbns: find_computer, "
+ "get query status, error %d", rc);
+ }
+
/* free the search results */
(void) ldap_msgfree(res);
+
return (rc);
}
@@ -1594,6 +1673,8 @@ smb_ads_find_computer(smb_ads_handle_t *ah, char *dn)
smb_ads_avpair_t avpair;
avpair.avp_attr = SMB_ADS_ATTR_DN;
+ avpair.avp_val = NULL;
+
smb_ads_get_default_comp_container_dn(ah, dn, SMB_ADS_DN_MAX);
stat = smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_ONELEVEL,
dn);
@@ -1659,6 +1740,7 @@ smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn)
int kvno = 1;
avpair.avp_attr = SMB_ADS_ATTR_KVNO;
+ avpair.avp_val = NULL;
if (smb_ads_lookup_computer_n_attr(ah, &avpair,
LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) {
kvno = atoi(avpair.avp_val);
@@ -1713,6 +1795,8 @@ smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd)
rc = smb_ads_open_main(&ah, domain, user, usr_passwd);
if (rc != 0) {
+ const char *s = smb_ads_strerror(rc);
+ syslog(LOG_ERR, "smb_ads_join: open_main, error %s", s);
smb_ccache_remove(SMB_CCACHE_PATH);
return (rc);
}
@@ -1727,6 +1811,8 @@ smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd)
switch (qstat) {
case SMB_ADS_STAT_FOUND:
new_acct = B_FALSE;
+ syslog(LOG_INFO, "smb_ads_join: machine account found."
+ " Updating: %s", dn);
if (smb_ads_modify_computer(ah, dclevel, dn) != 0) {
smb_ads_close(ah);
smb_ccache_remove(SMB_CCACHE_PATH);
@@ -1737,6 +1823,8 @@ smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd)
case SMB_ADS_STAT_NOT_FOUND:
new_acct = B_TRUE;
smb_ads_get_default_comp_dn(ah, dn, SMB_ADS_DN_MAX);
+ syslog(LOG_INFO, "smb_ads_join: machine account not found."
+ " Creating: %s", dn);
if (smb_ads_add_computer(ah, dclevel, dn) != 0) {
smb_ads_close(ah);
smb_ccache_remove(SMB_CCACHE_PATH);
@@ -1745,6 +1833,7 @@ smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd)
break;
default:
+ syslog(LOG_INFO, "smb_ads_find_computer, rc=%d", qstat);
if (qstat == SMB_ADS_STAT_DUP)
rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT;
else
diff --git a/usr/src/pkg/manifests/system-test-ostest.p5m b/usr/src/pkg/manifests/system-test-ostest.p5m
index 2315c52658..100f109732 100644
--- a/usr/src/pkg/manifests/system-test-ostest.p5m
+++ b/usr/src/pkg/manifests/system-test-ostest.p5m
@@ -103,6 +103,8 @@ file path=opt/os-tests/tests/poll/poll_test mode=0555
dir path=opt/os-tests/tests/portfs
file path=opt/os-tests/tests/portfs/file_assoc.32 mode=0555
file path=opt/os-tests/tests/portfs/file_assoc.64 mode=0555
+dir path=opt/os-tests/tests/regression
+file path=opt/os-tests/tests/regression/illumos-15031 mode=0555
dir path=opt/os-tests/tests/sdevfs
file path=opt/os-tests/tests/sdevfs/sdevfs_eisdir mode=0555
dir path=opt/os-tests/tests/secflags
diff --git a/usr/src/test/os-tests/runfiles/default.run b/usr/src/test/os-tests/runfiles/default.run
index e5dcabb596..49156ea42b 100644
--- a/usr/src/test/os-tests/runfiles/default.run
+++ b/usr/src/test/os-tests/runfiles/default.run
@@ -146,3 +146,6 @@ tests = ['coretests']
[/opt/os-tests/tests/portfs]
tests = ['file_assoc.32', 'file_assoc.64']
+
+[/opt/os-tests/tests/regression]
+tests = ['illumos-15031']
diff --git a/usr/src/test/os-tests/tests/Makefile b/usr/src/test/os-tests/tests/Makefile
index 6dfe29087d..832b750d48 100644
--- a/usr/src/test/os-tests/tests/Makefile
+++ b/usr/src/test/os-tests/tests/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
pf_key \
poll \
portfs \
+ regression \
sdevfs \
secflags \
sigqueue \
diff --git a/usr/src/test/os-tests/tests/regression/Makefile b/usr/src/test/os-tests/tests/regression/Makefile
new file mode 100644
index 0000000000..1536e83ca6
--- /dev/null
+++ b/usr/src/test/os-tests/tests/regression/Makefile
@@ -0,0 +1,53 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2022 Oxide Computer Company
+#
+
+PROGS = \
+ illumos-15031
+
+ROOTOPTDIR = $(ROOT)/opt/os-tests/tests
+ROOTOPTREGRESSION = $(ROOTOPTDIR)/regression
+ROOTOPTPROGS = $(PROGS:%=$(ROOTOPTREGRESSION)/%)
+
+include $(SRC)/cmd/Makefile.cmd
+
+CSTD=$(GNU_C99)
+
+.KEEP_STATE:
+
+all: $(PROGS)
+
+install: $(ROOTOPTPROGS)
+
+clean:
+
+$(ROOTOPTPROGS): $(PROGS) $(ROOTOPTREGRESSION)
+
+$(ROOTOPTDIR):
+ $(INS.dir)
+
+$(ROOTOPTREGRESSION): $(ROOTOPTDIR)
+ $(INS.dir)
+
+$(ROOTOPTREGRESSION)/%: %
+ $(INS.file)
+
+%: %.c
+ $(LINK.c) -o $@ $< $(LDLIBS)
+ $(POST_PROCESS)
+
+clobber:
+ $(RM) $(PROGS)
+
+FRC:
diff --git a/usr/src/test/os-tests/tests/regression/illumos-15031.c b/usr/src/test/os-tests/tests/regression/illumos-15031.c
new file mode 100644
index 0000000000..8f01ec6bc4
--- /dev/null
+++ b/usr/src/test/os-tests/tests/regression/illumos-15031.c
@@ -0,0 +1,85 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2022 Oxide Computer Company
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <port.h>
+#include <err.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/poll.h>
+#include <stdbool.h>
+
+static bool
+has_event(int pfd)
+{
+ port_event_t ev = { 0 };
+ timespec_t ts = { 0 };
+
+ /* Because of illumos 14912, more care needs to be taken here */
+ int res = port_get(pfd, &ev, &ts);
+ if (res != 0 || ev.portev_source == 0) {
+ return (false);
+ } else {
+ return (true);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int res;
+ int pipes[2];
+
+ res = pipe2(pipes, 0);
+ assert(res == 0);
+
+ int pfd = port_create();
+ assert(pfd >= 0);
+
+ res = port_associate(pfd, PORT_SOURCE_FD, (uintptr_t)pipes[1], POLLIN,
+ NULL);
+ assert(res == 0);
+
+ if (has_event(pfd)) {
+ errx(EXIT_FAILURE, "FAIL - unexpected early event");
+ }
+
+ char port_path[MAXPATHLEN];
+ (void) sprintf(port_path, "/proc/%d/fd/%d", getpid(), pfd);
+
+ /* incur the procfs FDINFO access */
+ struct stat sbuf;
+ res = lstat(port_path, &sbuf);
+ assert(res == 0);
+
+ /* write a byte to wake up the pipe */
+ (void) write(pipes[0], port_path, 1);
+
+ /*
+ * Check to see that the FDINFO access did not detach our registration
+ * for the event port.
+ */
+ if (!has_event(pfd)) {
+ errx(EXIT_FAILURE, "FAIL - no event found");
+ }
+
+ (void) printf("PASS\n");
+ return (EXIT_SUCCESS);
+}
diff --git a/usr/src/uts/common/fs/proc/prdata.h b/usr/src/uts/common/fs/proc/prdata.h
index 336b4b0ed9..6d8ac2e6ed 100644
--- a/usr/src/uts/common/fs/proc/prdata.h
+++ b/usr/src/uts/common/fs/proc/prdata.h
@@ -354,6 +354,7 @@ extern int pr_set(proc_t *, long);
extern int pr_unset(proc_t *, long);
extern void pr_sethold(prnode_t *, sigset_t *);
extern file_t *pr_getf(proc_t *, uint_t, short *);
+extern void pr_releasef(file_t *);
extern void pr_setfault(proc_t *, fltset_t *);
extern int prusrio(proc_t *, enum uio_rw, struct uio *, int);
extern int prreadargv(proc_t *, char *, size_t, size_t *);
diff --git a/usr/src/uts/common/fs/proc/prsubr.c b/usr/src/uts/common/fs/proc/prsubr.c
index 34e9c730f5..be41826b54 100644
--- a/usr/src/uts/common/fs/proc/prsubr.c
+++ b/usr/src/uts/common/fs/proc/prsubr.c
@@ -24,6 +24,7 @@
* Copyright 2019 Joyent, Inc.
* Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
* Copyright 2022 MNX Cloud, Inc.
+ * Copyright 2022 Oxide Computer Company
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
@@ -1622,6 +1623,54 @@ retry:
return (fp);
}
+
+/*
+ * Just as pr_getf() is a little unusual in how it goes about making the file_t
+ * safe for procfs consumers to access it, so too is pr_releasef() for safely
+ * releasing that "hold". The "hold" is unlike normal file descriptor activity
+ * -- procfs is just an interloper here, wanting access to the vnode_t without
+ * risk of a racing close() disrupting the state. Just as pr_getf() avoids some
+ * of the typical file_t behavior (such as auditing) when establishing its hold,
+ * so too should pr_releasef(). It should not go through the motions of
+ * closef() (since it is not a true close()) unless racing activity causes it to
+ * be the last actor holding the refcount above zero.
+ *
+ * Under normal circumstances, we expect to find file_t`f_count > 1 after
+ * the successful pr_getf() call. We are, after all, accessing a resource
+ * already held by the process in question. We would also expect to rarely race
+ * with a close() of the underlying fd, meaning that file_t`f_count > 1 would
+ * still holds at pr_releasef() time. That would mean we only need to decrement
+ * f_count, leaving it to the process to later close the fd (thus triggering
+ * VOP_CLOSE(), etc).
+ *
+ * It is only when that process manages to close() the fd while we have it
+ * "held" in procfs that we must make a trip through the traditional closef()
+ * logic to ensure proper tear-down of the file_t.
+ */
+void
+pr_releasef(file_t *fp)
+{
+ mutex_enter(&fp->f_tlock);
+ if (fp->f_count > 1) {
+ /*
+ * This is the most common case: The file is still held open by
+ * the process, and we simply need to release our hold by
+ * decrementing f_count
+ */
+ fp->f_count--;
+ mutex_exit(&fp->f_tlock);
+ } else {
+ /*
+ * A rare occasion: The process snuck a close() of this file
+ * while we were doing our business in procfs. Given that
+ * f_count == 1, we are the only one with a reference to the
+ * file_t and need to take a trip through closef() to free it.
+ */
+ mutex_exit(&fp->f_tlock);
+ (void) closef(fp);
+ }
+}
+
void
pr_object_name(char *name, vnode_t *vp, struct vattr *vattr)
{
diff --git a/usr/src/uts/common/fs/proc/prvnops.c b/usr/src/uts/common/fs/proc/prvnops.c
index 5e965b38c3..39f8e6f01e 100644
--- a/usr/src/uts/common/fs/proc/prvnops.c
+++ b/usr/src/uts/common/fs/proc/prvnops.c
@@ -25,6 +25,7 @@
* Copyright (c) 2017 by Delphix. All rights reserved.
* Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
* Copyright 2022 MNX Cloud, Inc.
+ * Copyright 2022 Oxide Computer Company
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
@@ -963,7 +964,7 @@ pr_read_fdinfo(prnode_t *pnp, uio_t *uiop, cred_t *cr)
error = prgetfdinfo(p, fp->f_vnode, fdinfo, cr, fp->f_cred, &data);
- (void) closef(fp);
+ pr_releasef(fp);
out:
if (error == 0)
@@ -3386,7 +3387,7 @@ prgetattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,
prunlock(pnp);
vap->va_size = prgetfdinfosize(p, fp->f_vnode, cr);
vap->va_nblocks = (fsblkcnt64_t)btod(vap->va_size);
- (void) closef(fp);
+ pr_releasef(fp);
return (0);
}
case PR_LWPDIR:
@@ -4497,8 +4498,9 @@ pr_lookup_fddir(vnode_t *dp, char *comp)
}
prunlock(dpnp);
- if (fp != NULL)
- (void) closef(fp);
+ if (fp != NULL) {
+ pr_releasef(fp);
+ }
if (vp == NULL) {
prfreenode(pnp);