/* Copyright 1996, 1998 The Open Group Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of The Open Group shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from The Open Group. */ #ifdef HAVE_DIX_CONFIG_H #include #endif #include "dixstruct.h" #include "extnsionst.h" #include "windowstr.h" #include "inputstr.h" #include "scrnintstr.h" #include "gcstruct.h" #include "colormapst.h" #include "propertyst.h" #include "xacestr.h" #include "securitysrv.h" #include #include #include #ifdef XAPPGROUP #include "appgroup.h" #endif #include /* for file reading operations */ #include /* for XA_STRING */ #ifndef DEFAULTPOLICYFILE # define DEFAULTPOLICYFILE NULL #endif #if defined(WIN32) || defined(__CYGWIN__) #include #undef index #endif #include "modinit.h" static int SecurityErrorBase; /* first Security error number */ static int SecurityEventBase; /* first Security event number */ static int securityClientPrivateIndex; static int securityExtnsnPrivateIndex; /* this is what we store as client security state */ typedef struct { unsigned int trustLevel; XID authId; } SecurityClientStateRec; #define STATEVAL(extnsn) \ ((extnsn)->devPrivates[securityExtnsnPrivateIndex].val) #define STATEPTR(client) \ ((client)->devPrivates[securityClientPrivateIndex].ptr) #define TRUSTLEVEL(client) \ (((SecurityClientStateRec*)STATEPTR(client))->trustLevel) #define AUTHID(client) \ (((SecurityClientStateRec*)STATEPTR(client))->authId) static CallbackListPtr SecurityValidateGroupCallback = NULL; RESTYPE SecurityAuthorizationResType; /* resource type for authorizations */ static RESTYPE RTEventClient; #define CALLBACK(name) static void \ name(CallbackListPtr *pcbl, pointer nulldata, pointer calldata) /* SecurityAudit * * Arguments: * format is the formatting string to be used to interpret the * remaining arguments. * * Returns: nothing. * * Side Effects: * Writes the message to the log file if security logging is on. */ static void SecurityAudit(char *format, ...) { va_list args; if (auditTrailLevel < SECURITY_AUDIT_LEVEL) return; va_start(args, format); VAuditF(format, args); va_end(args); } /* SecurityAudit */ #define rClient(obj) (clients[CLIENT_ID((obj)->resource)]) /* SecurityDeleteAuthorization * * Arguments: * value is the authorization to delete. * id is its resource ID. * * Returns: Success. * * Side Effects: * Frees everything associated with the authorization. */ static int SecurityDeleteAuthorization( pointer value, XID id) { SecurityAuthorizationPtr pAuth = (SecurityAuthorizationPtr)value; unsigned short name_len, data_len; char *name, *data; int status; int i; OtherClientsPtr pEventClient; /* Remove the auth using the os layer auth manager */ status = AuthorizationFromID(pAuth->id, &name_len, &name, &data_len, &data); assert(status); status = RemoveAuthorization(name_len, name, data_len, data); assert(status); (void)status; /* free the auth timer if there is one */ if (pAuth->timer) TimerFree(pAuth->timer); /* send revoke events */ while ((pEventClient = pAuth->eventClients)) { /* send revocation event event */ ClientPtr client = rClient(pEventClient); if (!client->clientGone) { xSecurityAuthorizationRevokedEvent are; are.type = SecurityEventBase + XSecurityAuthorizationRevoked; are.sequenceNumber = client->sequence; are.authId = pAuth->id; WriteEventsToClient(client, 1, (xEvent *)&are); } FreeResource(pEventClient->resource, RT_NONE); } /* kill all clients using this auth */ for (i = 1; iid)) CloseDownClient(clients[i]); } SecurityAudit("revoked authorization ID %d\n", pAuth->id); xfree(pAuth); return Success; } /* SecurityDeleteAuthorization */ /* resource delete function for RTEventClient */ static int SecurityDeleteAuthorizationEventClient( pointer value, XID id) { OtherClientsPtr pEventClient, prev = NULL; SecurityAuthorizationPtr pAuth = (SecurityAuthorizationPtr)value; for (pEventClient = pAuth->eventClients; pEventClient; pEventClient = pEventClient->next) { if (pEventClient->resource == id) { if (prev) prev->next = pEventClient->next; else pAuth->eventClients = pEventClient->next; xfree(pEventClient); return(Success); } prev = pEventClient; } /*NOTREACHED*/ return -1; /* make compiler happy */ } /* SecurityDeleteAuthorizationEventClient */ /* SecurityComputeAuthorizationTimeout * * Arguments: * pAuth is the authorization for which we are computing the timeout * seconds is the number of seconds we want to wait * * Returns: * the number of milliseconds that the auth timer should be set to * * Side Effects: * Sets pAuth->secondsRemaining to any "overflow" amount of time * that didn't fit in 32 bits worth of milliseconds */ static CARD32 SecurityComputeAuthorizationTimeout( SecurityAuthorizationPtr pAuth, unsigned int seconds) { /* maxSecs is the number of full seconds that can be expressed in * 32 bits worth of milliseconds */ CARD32 maxSecs = (CARD32)(~0) / (CARD32)MILLI_PER_SECOND; if (seconds > maxSecs) { /* only come here if we want to wait more than 49 days */ pAuth->secondsRemaining = seconds - maxSecs; return maxSecs * MILLI_PER_SECOND; } else { /* by far the common case */ pAuth->secondsRemaining = 0; return seconds * MILLI_PER_SECOND; } } /* SecurityStartAuthorizationTimer */ /* SecurityAuthorizationExpired * * This function is passed as an argument to TimerSet and gets called from * the timer manager in the os layer when its time is up. * * Arguments: * timer is the timer for this authorization. * time is the current time. * pval is the authorization whose time is up. * * Returns: * A new time delay in milliseconds if the timer should wait some * more, else zero. * * Side Effects: * Frees the authorization resource if the timeout period is really * over, otherwise recomputes pAuth->secondsRemaining. */ static CARD32 SecurityAuthorizationExpired( OsTimerPtr timer, CARD32 time, pointer pval) { SecurityAuthorizationPtr pAuth = (SecurityAuthorizationPtr)pval; assert(pAuth->timer == timer); if (pAuth->secondsRemaining) { return SecurityComputeAuthorizationTimeout(pAuth, pAuth->secondsRemaining); } else { FreeResource(pAuth->id, RT_NONE); return 0; } } /* SecurityAuthorizationExpired */ /* SecurityStartAuthorizationTimer * * Arguments: * pAuth is the authorization whose timer should be started. * * Returns: nothing. * * Side Effects: * A timer is started, set to expire after the timeout period for * this authorization. When it expires, the function * SecurityAuthorizationExpired will be called. */ static void SecurityStartAuthorizationTimer( SecurityAuthorizationPtr pAuth) { pAuth->timer = TimerSet(pAuth->timer, 0, SecurityComputeAuthorizationTimeout(pAuth, pAuth->timeout), SecurityAuthorizationExpired, pAuth); } /* SecurityStartAuthorizationTimer */ /* Proc functions all take a client argument, execute the request in * client->requestBuffer, and return a protocol error status. */ static int ProcSecurityQueryVersion( ClientPtr client) { /* REQUEST(xSecurityQueryVersionReq); */ xSecurityQueryVersionReply rep; /* paranoia: this "can't happen" because this extension is hidden * from untrusted clients, but just in case... */ if (TRUSTLEVEL(client) != XSecurityClientTrusted) return BadRequest; REQUEST_SIZE_MATCH(xSecurityQueryVersionReq); rep.type = X_Reply; rep.sequenceNumber = client->sequence; rep.length = 0; rep.majorVersion = SECURITY_MAJOR_VERSION; rep.minorVersion = SECURITY_MINOR_VERSION; if(client->swapped) { register char n; swaps(&rep.sequenceNumber, n); swaps(&rep.majorVersion, n); swaps(&rep.minorVersion, n); } (void)WriteToClient(client, SIZEOF(xSecurityQueryVersionReply), (char *)&rep); return (client->noClientException); } /* ProcSecurityQueryVersion */ static int SecurityEventSelectForAuthorization( SecurityAuthorizationPtr pAuth, ClientPtr client, Mask mask) { OtherClients *pEventClient; for (pEventClient = pAuth->eventClients; pEventClient; pEventClient = pEventClient->next) { if (SameClient(pEventClient, client)) { if (mask == 0) FreeResource(pEventClient->resource, RT_NONE); else pEventClient->mask = mask; return Success; } } pEventClient = (OtherClients *) xalloc(sizeof(OtherClients)); if (!pEventClient) return BadAlloc; pEventClient->mask = mask; pEventClient->resource = FakeClientID(client->index); pEventClient->next = pAuth->eventClients; if (!AddResource(pEventClient->resource, RTEventClient, (pointer)pAuth)) { xfree(pEventClient); return BadAlloc; } pAuth->eventClients = pEventClient; return Success; } /* SecurityEventSelectForAuthorization */ static int ProcSecurityGenerateAuthorization( ClientPtr client) { REQUEST(xSecurityGenerateAuthorizationReq); int len; /* request length in CARD32s*/ Bool removeAuth = FALSE; /* if bailout, call RemoveAuthorization? */ SecurityAuthorizationPtr pAuth = NULL; /* auth we are creating */ int err; /* error to return from this function */ XID authId; /* authorization ID assigned by os layer */ xSecurityGenerateAuthorizationReply rep; /* reply struct */ unsigned int trustLevel; /* trust level of new auth */ XID group; /* group of new auth */ CARD32 timeout; /* timeout of new auth */ CARD32 *values; /* list of supplied attributes */ char *protoname; /* auth proto name sent in request */ char *protodata; /* auth proto data sent in request */ unsigned int authdata_len; /* # bytes of generated auth data */ char *pAuthdata; /* generated auth data */ Mask eventMask; /* what events on this auth does client want */ /* paranoia: this "can't happen" because this extension is hidden * from untrusted clients, but just in case... */ if (TRUSTLEVEL(client) != XSecurityClientTrusted) return BadRequest; /* check request length */ REQUEST_AT_LEAST_SIZE(xSecurityGenerateAuthorizationReq); len = SIZEOF(xSecurityGenerateAuthorizationReq) >> 2; len += (stuff->nbytesAuthProto + (unsigned)3) >> 2; len += (stuff->nbytesAuthData + (unsigned)3) >> 2; values = ((CARD32 *)stuff) + len; len += Ones(stuff->valueMask); if (client->req_len != len) return BadLength; /* check valuemask */ if (stuff->valueMask & ~XSecurityAllAuthorizationAttributes) { client->errorValue = stuff->valueMask; return BadValue; } /* check timeout */ timeout = 60; if (stuff->valueMask & XSecurityTimeout) { timeout = *values++; } /* check trustLevel */ trustLevel = XSecurityClientUntrusted; if (stuff->valueMask & XSecurityTrustLevel) { trustLevel = *values++; if (trustLevel != XSecurityClientTrusted && trustLevel != XSecurityClientUntrusted) { client->errorValue = trustLevel; return BadValue; } } /* check group */ group = None; if (stuff->valueMask & XSecurityGroup) { group = *values++; if (SecurityValidateGroupCallback) { SecurityValidateGroupInfoRec vgi; vgi.group = group; vgi.valid = FALSE; CallCallbacks(&SecurityValidateGroupCallback, (pointer)&vgi); /* if nobody said they recognized it, it's an error */ if (!vgi.valid) { client->errorValue = group; return BadValue; } } } /* check event mask */ eventMask = 0; if (stuff->valueMask & XSecurityEventMask) { eventMask = *values++; if (eventMask & ~XSecurityAllEventMasks) { client->errorValue = eventMask; return BadValue; } } protoname = (char *)&stuff[1]; protodata = protoname + ((stuff->nbytesAuthProto + (unsigned)3) >> 2); /* call os layer to generate the authorization */ authId = GenerateAuthorization(stuff->nbytesAuthProto, protoname, stuff->nbytesAuthData, protodata, &authdata_len, &pAuthdata); if ((XID) ~0L == authId) { err = SecurityErrorBase + XSecurityBadAuthorizationProtocol; goto bailout; } /* now that we've added the auth, remember to remove it if we have to * abort the request for some reason (like allocation failure) */ removeAuth = TRUE; /* associate additional information with this auth ID */ pAuth = (SecurityAuthorizationPtr)xalloc(sizeof(SecurityAuthorizationRec)); if (!pAuth) { err = BadAlloc; goto bailout; } /* fill in the auth fields */ pAuth->id = authId; pAuth->timeout = timeout; pAuth->group = group; pAuth->trustLevel = trustLevel; pAuth->refcnt = 0; /* the auth was just created; nobody's using it yet */ pAuth->secondsRemaining = 0; pAuth->timer = NULL; pAuth->eventClients = NULL; /* handle event selection */ if (eventMask) { err = SecurityEventSelectForAuthorization(pAuth, client, eventMask); if (err != Success) goto bailout; } if (!AddResource(authId, SecurityAuthorizationResType, pAuth)) { err = BadAlloc; goto bailout; } /* start the timer ticking */ if (pAuth->timeout != 0) SecurityStartAuthorizationTimer(pAuth); /* tell client the auth id and data */ rep.type = X_Reply; rep.length = (authdata_len + 3) >> 2; rep.sequenceNumber = client->sequence; rep.authId = authId; rep.dataLength = authdata_len; if (client->swapped) { register char n; swapl(&rep.length, n); swaps(&rep.sequenceNumber, n); swapl(&rep.authId, n); swaps(&rep.dataLength, n); } WriteToClient(client, SIZEOF(xSecurityGenerateAuthorizationReply), (char *)&rep); WriteToClient(client, authdata_len, pAuthdata); SecurityAudit("client %d generated authorization %d trust %d timeout %d group %d events %d\n", client->index, pAuth->id, pAuth->trustLevel, pAuth->timeout, pAuth->group, eventMask); /* the request succeeded; don't call RemoveAuthorization or free pAuth */ removeAuth = FALSE; pAuth = NULL; err = client->noClientException; bailout: if (removeAuth) RemoveAuthorization(stuff->nbytesAuthProto, protoname, authdata_len, pAuthdata); if (pAuth) xfree(pAuth); return err; } /* ProcSecurityGenerateAuthorization */ static int ProcSecurityRevokeAuthorization( ClientPtr client) { REQUEST(xSecurityRevokeAuthorizationReq); SecurityAuthorizationPtr pAuth; /* paranoia: this "can't happen" because this extension is hidden * from untrusted clients, but just in case... */ if (TRUSTLEVEL(client) != XSecurityClientTrusted) return BadRequest; REQUEST_SIZE_MATCH(xSecurityRevokeAuthorizationReq); pAuth = (SecurityAuthorizationPtr)SecurityLookupIDByType(client, stuff->authId, SecurityAuthorizationResType, DixDestroyAccess); if (!pAuth) return SecurityErrorBase + XSecurityBadAuthorization; FreeResource(stuff->authId, RT_NONE); return Success; } /* ProcSecurityRevokeAuthorization */ static int ProcSecurityDispatch( ClientPtr client) { REQUEST(xReq); switch (stuff->data) { case X_SecurityQueryVersion: return ProcSecurityQueryVersion(client); case X_SecurityGenerateAuthorization: return ProcSecurityGenerateAuthorization(client); case X_SecurityRevokeAuthorization: return ProcSecurityRevokeAuthorization(client); default: return BadRequest; } } /* ProcSecurityDispatch */ static int SProcSecurityQueryVersion( ClientPtr client) { REQUEST(xSecurityQueryVersionReq); register char n; swaps(&stuff->length, n); REQUEST_SIZE_MATCH(xSecurityQueryVersionReq); swaps(&stuff->majorVersion, n); swaps(&stuff->minorVersion,n); return ProcSecurityQueryVersion(client); } /* SProcSecurityQueryVersion */ static int SProcSecurityGenerateAuthorization( ClientPtr client) { REQUEST(xSecurityGenerateAuthorizationReq); register char n; CARD32 *values; unsigned long nvalues; swaps(&stuff->length, n); REQUEST_AT_LEAST_SIZE(xSecurityGenerateAuthorizationReq); swaps(&stuff->nbytesAuthProto, n); swaps(&stuff->nbytesAuthData, n); swapl(&stuff->valueMask, n); values = (CARD32 *)(&stuff[1]) + ((stuff->nbytesAuthProto + (unsigned)3) >> 2) + ((stuff->nbytesAuthData + (unsigned)3) >> 2); nvalues = (((CARD32 *)stuff) + stuff->length) - values; SwapLongs(values, nvalues); return ProcSecurityGenerateAuthorization(client); } /* SProcSecurityGenerateAuthorization */ static int SProcSecurityRevokeAuthorization( ClientPtr client) { REQUEST(xSecurityRevokeAuthorizationReq); register char n; swaps(&stuff->length, n); REQUEST_SIZE_MATCH(xSecurityRevokeAuthorizationReq); swapl(&stuff->authId, n); return ProcSecurityRevokeAuthorization(client); } /* SProcSecurityRevokeAuthorization */ static int SProcSecurityDispatch( ClientPtr client) { REQUEST(xReq); switch (stuff->data) { case X_SecurityQueryVersion: return SProcSecurityQueryVersion(client); case X_SecurityGenerateAuthorization: return SProcSecurityGenerateAuthorization(client); case X_SecurityRevokeAuthorization: return SProcSecurityRevokeAuthorization(client); default: return BadRequest; } } /* SProcSecurityDispatch */ static void SwapSecurityAuthorizationRevokedEvent( xSecurityAuthorizationRevokedEvent *from, xSecurityAuthorizationRevokedEvent *to) { to->type = from->type; to->detail = from->detail; cpswaps(from->sequenceNumber, to->sequenceNumber); cpswapl(from->authId, to->authId); } /* SecurityDetermineEventPropogationLimits * * This is a helper function for SecurityCheckDeviceAccess. * * Arguments: * dev is the device for which the starting and stopping windows for * event propogation should be determined. * The values pointed to by ppWin and ppStopWin are not used. * * Returns: * ppWin is filled in with a pointer to the window at which event * propogation for the given device should start given the current * state of the server (pointer position, window layout, etc.) * ppStopWin is filled in with the window at which event propogation * should stop; events should not go to ppStopWin. * * Side Effects: none. */ static void SecurityDetermineEventPropogationLimits( DeviceIntPtr dev, WindowPtr *ppWin, WindowPtr *ppStopWin) { WindowPtr pFocusWin = dev->focus ? dev->focus->win : NoneWin; if (pFocusWin == NoneWin) { /* no focus -- events don't go anywhere */ *ppWin = *ppStopWin = NULL; return; } if (pFocusWin == PointerRootWin) { /* focus follows the pointer */ *ppWin = GetSpriteWindow(); *ppStopWin = NULL; /* propogate all the way to the root */ } else { /* a real window is set for the focus */ WindowPtr pSpriteWin = GetSpriteWindow(); *ppStopWin = pFocusWin->parent; /* don't go past the focus window */ /* if the pointer is in a subwindow of the focus window, start * at that subwindow, else start at the focus window itself */ if (IsParent(pFocusWin, pSpriteWin)) *ppWin = pSpriteWin; else *ppWin = pFocusWin; } } /* SecurityDetermineEventPropogationLimits */ /* SecurityCheckDeviceAccess * * Arguments: * client is the client attempting to access a device. * dev is the device being accessed. * fromRequest is TRUE if the device access is a direct result of * the client executing some request and FALSE if it is a * result of the server trying to send an event (e.g. KeymapNotify) * to the client. * Returns: * TRUE if the device access should be allowed, else FALSE. * * Side Effects: * An audit message is generated if access is denied. */ CALLBACK(SecurityCheckDeviceAccess) { XaceDeviceAccessRec *rec = (XaceDeviceAccessRec*)calldata; ClientPtr client = rec->client; DeviceIntPtr dev = rec->dev; Bool fromRequest = rec->fromRequest; WindowPtr pWin, pStopWin; Bool untrusted_got_event; Bool found_event_window; Mask eventmask; int reqtype = 0; /* trusted clients always allowed to do anything */ if (TRUSTLEVEL(client) == XSecurityClientTrusted) return; /* device security other than keyboard is not implemented yet */ if (dev != inputInfo.keyboard) return; /* some untrusted client wants access */ if (fromRequest) { reqtype = ((xReq *)client->requestBuffer)->reqType; switch (reqtype) { /* never allow these */ case X_ChangeKeyboardMapping: case X_ChangeKeyboardControl: case X_SetModifierMapping: SecurityAudit("client %d attempted request %d\n", client->index, reqtype); rec->rval = FALSE; return; default: break; } } untrusted_got_event = FALSE; found_event_window = FALSE; if (dev->grab) { untrusted_got_event = (TRUSTLEVEL(rClient(dev->grab)) != XSecurityClientTrusted); } else { SecurityDetermineEventPropogationLimits(dev, &pWin, &pStopWin); eventmask = KeyPressMask | KeyReleaseMask; while ( (pWin != pStopWin) && !found_event_window) { OtherClients *other; if (pWin->eventMask & eventmask) { found_event_window = TRUE; client = wClient(pWin); if (TRUSTLEVEL(client) != XSecurityClientTrusted) { untrusted_got_event = TRUE; } } if (wOtherEventMasks(pWin) & eventmask) { found_event_window = TRUE; for (other = wOtherClients(pWin); other; other = other->next) { if (other->mask & eventmask) { client = rClient(other); if (TRUSTLEVEL(client) != XSecurityClientTrusted) { untrusted_got_event = TRUE; break; } } } } if (wDontPropagateMask(pWin) & eventmask) break; pWin = pWin->parent; } /* while propogating the event */ } /* allow access by untrusted clients only if an event would have gone * to an untrusted client */ if (!untrusted_got_event) { char *devname = dev->name; if (!devname) devname = "unnamed"; if (fromRequest) SecurityAudit("client %d attempted request %d device %d (%s)\n", client->index, reqtype, dev->id, devname); else SecurityAudit("client %d attempted to access device %d (%s)\n", client->index, dev->id, devname); rec->rval = FALSE; } return; } /* SecurityCheckDeviceAccess */ /* SecurityAuditResourceIDAccess * * Arguments: * client is the client doing the resource access. * id is the resource id. * * Returns: NULL * * Side Effects: * An audit message is generated with details of the denied * resource access. */ static pointer SecurityAuditResourceIDAccess( ClientPtr client, XID id) { int cid = CLIENT_ID(id); int reqtype = ((xReq *)client->requestBuffer)->reqType; switch (reqtype) { case X_ChangeProperty: case X_DeleteProperty: case X_GetProperty: { xChangePropertyReq *req = (xChangePropertyReq *)client->requestBuffer; int propertyatom = req->property; char *propertyname = NameForAtom(propertyatom); SecurityAudit("client %d attempted request %d with window 0x%x property %s of client %d\n", client->index, reqtype, id, propertyname, cid); break; } default: { SecurityAudit("client %d attempted request %d with resource 0x%x of client %d\n", client->index, reqtype, id, cid); break; } } return NULL; } /* SecurityAuditResourceIDAccess */ /* SecurityCheckResourceIDAccess * * This function gets plugged into client->CheckAccess and is called from * SecurityLookupIDByType/Class to determine if the client can access the * resource. * * Arguments: * client is the client doing the resource access. * id is the resource id. * rtype is its type or class. * access_mode represents the intended use of the resource; see * resource.h. * res is a pointer to the resource structure for this resource. * * Returns: * If access is granted, the value of rval that was passed in, else FALSE. * * Side Effects: * Disallowed resource accesses are audited. */ CALLBACK(SecurityCheckResourceIDAccess) { XaceResourceAccessRec *rec = (XaceResourceAccessRec*)calldata; ClientPtr client = rec->client; XID id = rec->id; RESTYPE rtype = rec->rtype; Mask access_mode = rec->access_mode; pointer rval = rec->res; int cid, reqtype; if (TRUSTLEVEL(client) == XSecurityClientTrusted || DixUnknownAccess == access_mode) return; /* for compatibility, we have to allow access */ cid = CLIENT_ID(id); reqtype = ((xReq *)client->requestBuffer)->reqType; switch (reqtype) { /* these are always allowed */ case X_QueryTree: case X_TranslateCoords: case X_GetGeometry: /* property access is controlled in SecurityCheckPropertyAccess */ case X_GetProperty: case X_ChangeProperty: case X_DeleteProperty: case X_RotateProperties: case X_ListProperties: return; default: break; } if (cid != 0) { /* not a server-owned resource */ /* * The following 'if' restricts clients to only access resources at * the same trustLevel. Since there are currently only two trust levels, * and trusted clients never call this function, this degenerates into * saying that untrusted clients can only access resources of other * untrusted clients. One way to add the notion of groups would be to * allow values other than Trusted (0) and Untrusted (1) for this field. * Clients at the same trust level would be able to use each other's * resources, but not those of clients at other trust levels. I haven't * tried it, but this probably mostly works already. The obvious * competing alternative for grouping clients for security purposes is to * use app groups. dpw */ if (TRUSTLEVEL(client) == TRUSTLEVEL(clients[cid]) #ifdef XAPPGROUP || (RT_COLORMAP == rtype && XagDefaultColormap (client) == (Colormap) id) #endif ) return; else goto deny; } else /* server-owned resource - probably a default colormap or root window */ { if (RT_WINDOW == rtype || RC_DRAWABLE == rtype) { switch (reqtype) { /* the following operations are allowed on root windows */ case X_CreatePixmap: case X_CreateGC: case X_CreateWindow: case X_CreateColormap: case X_ListProperties: case X_GrabPointer: case X_UngrabButton: case X_QueryBestSize: case X_GetWindowAttributes: break; case X_SendEvent: { /* see if it is an event specified by the ICCCM */ xSendEventReq *req = (xSendEventReq *) (client->requestBuffer); if (req->propagate == xTrue || (req->eventMask != ColormapChangeMask && req->eventMask != StructureNotifyMask && req->eventMask != (SubstructureRedirectMask|SubstructureNotifyMask) ) || (req->event.u.u.type != UnmapNotify && req->event.u.u.type != ConfigureRequest && req->event.u.u.type != ClientMessage ) ) { /* not an ICCCM event */ goto deny; } break; } /* case X_SendEvent on root */ case X_ChangeWindowAttributes: { /* Allow selection of PropertyNotify and StructureNotify * events on the root. */ xChangeWindowAttributesReq *req = (xChangeWindowAttributesReq *)(client->requestBuffer); if (req->valueMask == CWEventMask) { CARD32 value = *((CARD32 *)(req + 1)); if ( (value & ~(PropertyChangeMask|StructureNotifyMask)) == 0) break; } goto deny; } /* case X_ChangeWindowAttributes on root */ default: { /* others not allowed */ goto deny; } } } /* end server-owned window or drawable */ else if (SecurityAuthorizationResType == rtype) { SecurityAuthorizationPtr pAuth = (SecurityAuthorizationPtr)rval; if (pAuth->trustLevel != TRUSTLEVEL(client)) goto deny; } else if (RT_COLORMAP != rtype) { /* don't allow anything else besides colormaps */ goto deny; } } return; deny: SecurityAuditResourceIDAccess(client, id); rec->rval = FALSE; /* deny access */ } /* SecurityCheckResourceIDAccess */ /* SecurityClientStateCallback * * Arguments: * pcbl is &ClientStateCallback. * nullata is NULL. * calldata is a pointer to a NewClientInfoRec (include/dixstruct.h) * which contains information about client state changes. * * Returns: nothing. * * Side Effects: * * If a new client is connecting, its authorization ID is copied to * client->authID. If this is a generated authorization, its reference * count is bumped, its timer is cancelled if it was running, and its * trustlevel is copied to TRUSTLEVEL(client). * * If a client is disconnecting and the client was using a generated * authorization, the authorization's reference count is decremented, and * if it is now zero, the timer for this authorization is started. */ CALLBACK(SecurityClientStateCallback) { NewClientInfoRec *pci = (NewClientInfoRec *)calldata; ClientPtr client = pci->client; switch (client->clientState) { case ClientStateInitial: TRUSTLEVEL(client) = XSecurityClientTrusted; AUTHID(client) = None; break; case ClientStateRunning: { XID authId = AuthorizationIDOfClient(client); SecurityAuthorizationPtr pAuth; TRUSTLEVEL(client) = XSecurityClientTrusted; AUTHID(client) = authId; pAuth = (SecurityAuthorizationPtr)LookupIDByType(authId, SecurityAuthorizationResType); if (pAuth) { /* it is a generated authorization */ pAuth->refcnt++; if (pAuth->refcnt == 1) { if (pAuth->timer) TimerCancel(pAuth->timer); } TRUSTLEVEL(client) = pAuth->trustLevel; } break; } case ClientStateGone: case ClientStateRetained: /* client disconnected */ { SecurityAuthorizationPtr pAuth; /* client may not have any state (bad authorization) */ if (!STATEPTR(client)) break; pAuth = (SecurityAuthorizationPtr)LookupIDByType(AUTHID(client), SecurityAuthorizationResType); if (pAuth) { /* it is a generated authorization */ pAuth->refcnt--; if (pAuth->refcnt == 0) { SecurityStartAuthorizationTimer(pAuth); } } break; } default: break; } } /* SecurityClientStateCallback */ CALLBACK(SecurityCheckDrawableAccess) { XaceDrawableAccessRec *rec = (XaceDrawableAccessRec*)calldata; if (TRUSTLEVEL(rec->client) != XSecurityClientTrusted) rec->rval = FALSE; } CALLBACK(SecurityCheckMapAccess) { XaceMapAccessRec *rec = (XaceMapAccessRec*)calldata; WindowPtr pWin = rec->pWin; if (STATEPTR(rec->client) && (TRUSTLEVEL(rec->client) != XSecurityClientTrusted) && (pWin->drawable.class == InputOnly) && pWin->parent && pWin->parent->parent && (TRUSTLEVEL(wClient(pWin->parent)) == XSecurityClientTrusted)) rec->rval = FALSE; } CALLBACK(SecurityCheckBackgrndAccess) { XaceMapAccessRec *rec = (XaceMapAccessRec*)calldata; if (TRUSTLEVEL(rec->client) != XSecurityClientTrusted) rec->rval = FALSE; } CALLBACK(SecurityCheckExtAccess) { XaceExtAccessRec *rec = (XaceExtAccessRec*)calldata; if ((TRUSTLEVEL(rec->client) != XSecurityClientTrusted) && !STATEVAL(rec->ext)) rec->rval = FALSE; } CALLBACK(SecurityCheckHostlistAccess) { XaceHostlistAccessRec *rec = (XaceHostlistAccessRec*)calldata; if (TRUSTLEVEL(rec->client) != XSecurityClientTrusted) { rec->rval = FALSE; if (rec->access_mode == DixWriteAccess) SecurityAudit("client %d attempted to change host access\n", rec->client->index); else SecurityAudit("client %d attempted to list hosts\n", rec->client->index); } } CALLBACK(SecurityDeclareExtSecure) { XaceDeclareExtSecureRec *rec = (XaceDeclareExtSecureRec*)calldata; /* security state for extensions is simply a boolean trust value */ STATEVAL(rec->ext) = rec->secure; } /**********************************************************************/ typedef struct _PropertyAccessRec { ATOM name; ATOM mustHaveProperty; char *mustHaveValue; char windowRestriction; #define SecurityAnyWindow 0 #define SecurityRootWindow 1 #define SecurityWindowWithProperty 2 char readAction; char writeAction; char destroyAction; struct _PropertyAccessRec *next; } PropertyAccessRec, *PropertyAccessPtr; static PropertyAccessPtr PropertyAccessList = NULL; static char SecurityDefaultAction = XaceErrorOperation; static char *SecurityPolicyFile = DEFAULTPOLICYFILE; static ATOM SecurityMaxPropertyName = 0; static char *SecurityKeywords[] = { #define SecurityKeywordComment 0 "#", #define SecurityKeywordProperty 1 "property", #define SecurityKeywordSitePolicy 2 "sitepolicy", #define SecurityKeywordRoot 3 "root", #define SecurityKeywordAny 4 "any" }; #define NUMKEYWORDS (sizeof(SecurityKeywords) / sizeof(char *)) #undef PROPDEBUG /*#define PROPDEBUG 1*/ static void SecurityFreePropertyAccessList(void) { while (PropertyAccessList) { PropertyAccessPtr freeit = PropertyAccessList; PropertyAccessList = PropertyAccessList->next; xfree(freeit); } } /* SecurityFreePropertyAccessList */ #define SecurityIsWhitespace(c) ( (c == ' ') || (c == '\t') || (c == '\n') ) static char * SecuritySkipWhitespace( char *p) { while (SecurityIsWhitespace(*p)) p++; return p; } /* SecuritySkipWhitespace */ static char * SecurityParseString( char **rest) { char *startOfString; char *s = *rest; char endChar = 0; s = SecuritySkipWhitespace(s); if (*s == '"' || *s == '\'') { endChar = *s++; startOfString = s; while (*s && (*s != endChar)) s++; } else { startOfString = s; while (*s && !SecurityIsWhitespace(*s)) s++; } if (*s) { *s = '\0'; *rest = s + 1; return startOfString; } else { *rest = s; return (endChar) ? NULL : startOfString; } } /* SecurityParseString */ static int SecurityParseKeyword( char **p) { int i; char *s = *p; s = SecuritySkipWhitespace(s); for (i = 0; i < NUMKEYWORDS; i++) { int len = strlen(SecurityKeywords[i]); if (strncmp(s, SecurityKeywords[i], len) == 0) { *p = s + len; return (i); } } *p = s; return -1; } /* SecurityParseKeyword */ static Bool SecurityParsePropertyAccessRule( char *p) { char *propname; char c; char action = SecurityDefaultAction; char readAction, writeAction, destroyAction; PropertyAccessPtr pacl, prev, cur; char *mustHaveProperty = NULL; char *mustHaveValue = NULL; Bool invalid; char windowRestriction; int size; int keyword; /* get property name */ propname = SecurityParseString(&p); if (!propname || (strlen(propname) == 0)) return FALSE; /* get window on which property must reside for rule to apply */ keyword = SecurityParseKeyword(&p); if (keyword == SecurityKeywordRoot) windowRestriction = SecurityRootWindow; else if (keyword == SecurityKeywordAny) windowRestriction = SecurityAnyWindow; else /* not root or any, must be a property name */ { mustHaveProperty = SecurityParseString(&p); if (!mustHaveProperty || (strlen(mustHaveProperty) == 0)) return FALSE; windowRestriction = SecurityWindowWithProperty; p = SecuritySkipWhitespace(p); if (*p == '=') { /* property value is specified too */ p++; /* skip over '=' */ mustHaveValue = SecurityParseString(&p); if (!mustHaveValue) return FALSE; } } /* get operations and actions */ invalid = FALSE; readAction = writeAction = destroyAction = SecurityDefaultAction; while ( (c = *p++) && !invalid) { switch (c) { case 'i': action = XaceIgnoreOperation; break; case 'a': action = XaceAllowOperation; break; case 'e': action = XaceErrorOperation; break; case 'r': readAction = action; break; case 'w': writeAction = action; break; case 'd': destroyAction = action; break; default : if (!SecurityIsWhitespace(c)) invalid = TRUE; break; } } if (invalid) return FALSE; /* We've successfully collected all the information needed for this * property access rule. Now record it in a PropertyAccessRec. */ size = sizeof(PropertyAccessRec); /* If there is a property value string, allocate space for it * right after the PropertyAccessRec. */ if (mustHaveValue) size += strlen(mustHaveValue) + 1; pacl = (PropertyAccessPtr)Xalloc(size); if (!pacl) return FALSE; pacl->name = MakeAtom(propname, strlen(propname), TRUE); if (pacl->name == BAD_RESOURCE) { Xfree(pacl); return FALSE; } if (mustHaveProperty) { pacl->mustHaveProperty = MakeAtom(mustHaveProperty, strlen(mustHaveProperty), TRUE); if (pacl->mustHaveProperty == BAD_RESOURCE) { Xfree(pacl); return FALSE; } } else pacl->mustHaveProperty = 0; if (mustHaveValue) { pacl->mustHaveValue = (char *)(pacl + 1); strcpy(pacl->mustHaveValue, mustHaveValue); } else pacl->mustHaveValue = NULL; SecurityMaxPropertyName = max(SecurityMaxPropertyName, pacl->name); pacl->windowRestriction = windowRestriction; pacl->readAction = readAction; pacl->writeAction = writeAction; pacl->destroyAction = destroyAction; /* link the new rule into the list of rules in order of increasing * property name (atom) value to make searching easier */ for (prev = NULL, cur = PropertyAccessList; cur && cur->name <= pacl->name; prev = cur, cur = cur->next) ; if (!prev) { pacl->next = cur; PropertyAccessList = pacl; } else { prev->next = pacl; pacl->next = cur; } return TRUE; } /* SecurityParsePropertyAccessRule */ static char **SecurityPolicyStrings = NULL; static int nSecurityPolicyStrings = 0; static Bool SecurityParseSitePolicy( char *p) { char *policyStr = SecurityParseString(&p); char *copyPolicyStr; char **newStrings; if (!policyStr) return FALSE; copyPolicyStr = (char *)Xalloc(strlen(policyStr) + 1); if (!copyPolicyStr) return TRUE; strcpy(copyPolicyStr, policyStr); newStrings = (char **)Xrealloc(SecurityPolicyStrings, sizeof (char *) * (nSecurityPolicyStrings + 1)); if (!newStrings) { Xfree(copyPolicyStr); return TRUE; } SecurityPolicyStrings = newStrings; SecurityPolicyStrings[nSecurityPolicyStrings++] = copyPolicyStr; return TRUE; } /* SecurityParseSitePolicy */ char ** SecurityGetSitePolicyStrings(n) int *n; { *n = nSecurityPolicyStrings; return SecurityPolicyStrings; } /* SecurityGetSitePolicyStrings */ static void SecurityFreeSitePolicyStrings(void) { if (SecurityPolicyStrings) { assert(nSecurityPolicyStrings); while (nSecurityPolicyStrings--) { Xfree(SecurityPolicyStrings[nSecurityPolicyStrings]); } Xfree(SecurityPolicyStrings); SecurityPolicyStrings = NULL; nSecurityPolicyStrings = 0; } } /* SecurityFreeSitePolicyStrings */ static void SecurityLoadPropertyAccessList(void) { FILE *f; int lineNumber = 0; SecurityMaxPropertyName = 0; if (!SecurityPolicyFile) return; f = Fopen(SecurityPolicyFile, "r"); if (!f) { ErrorF("error opening security policy file %s\n", SecurityPolicyFile); return; } while (!feof(f)) { char buf[200]; Bool validLine; char *p; if (!(p = fgets(buf, sizeof(buf), f))) break; lineNumber++; /* if first line, check version number */ if (lineNumber == 1) { char *v = SecurityParseString(&p); if (strcmp(v, SECURITY_POLICY_FILE_VERSION) != 0) { ErrorF("%s: invalid security policy file version, ignoring file\n", SecurityPolicyFile); break; } validLine = TRUE; } else { switch (SecurityParseKeyword(&p)) { case SecurityKeywordComment: validLine = TRUE; break; case SecurityKeywordProperty: validLine = SecurityParsePropertyAccessRule(p); break; case SecurityKeywordSitePolicy: validLine = SecurityParseSitePolicy(p); break; default: validLine = (*p == '\0'); /* blank lines OK, others not */ break; } } if (!validLine) ErrorF("Line %d of %s invalid, ignoring\n", lineNumber, SecurityPolicyFile); } /* end while more input */ #ifdef PROPDEBUG { PropertyAccessPtr pacl; char *op = "aie"; for (pacl = PropertyAccessList; pacl; pacl = pacl->next) { ErrorF("property %s ", NameForAtom(pacl->name)); switch (pacl->windowRestriction) { case SecurityAnyWindow: ErrorF("any "); break; case SecurityRootWindow: ErrorF("root "); break; case SecurityWindowWithProperty: { ErrorF("%s ", NameForAtom(pacl->mustHaveProperty)); if (pacl->mustHaveValue) ErrorF(" = \"%s\" ", pacl->mustHaveValue); } break; } ErrorF("%cr %cw %cd\n", op[pacl->readAction], op[pacl->writeAction], op[pacl->destroyAction]); } } #endif /* PROPDEBUG */ Fclose(f); } /* SecurityLoadPropertyAccessList */ static Bool SecurityMatchString( char *ws, char *cs) { while (*ws && *cs) { if (*ws == '*') { Bool match = FALSE; ws++; while (!(match = SecurityMatchString(ws, cs)) && *cs) { cs++; } return match; } else if (*ws == *cs) { ws++; cs++; } else break; } return ( ( (*ws == '\0') || ((*ws == '*') && *(ws+1) == '\0') ) && (*cs == '\0') ); } /* SecurityMatchString */ #ifdef PROPDEBUG #include #include #endif CALLBACK(SecurityCheckPropertyAccess) { XacePropertyAccessRec *rec = (XacePropertyAccessRec*)calldata; ClientPtr client = rec->client; WindowPtr pWin = rec->pWin; ATOM propertyName = rec->propertyName; Mask access_mode = rec->access_mode; PropertyAccessPtr pacl; char action = SecurityDefaultAction; /* if client trusted or window untrusted, allow operation */ if ( (TRUSTLEVEL(client) == XSecurityClientTrusted) || (TRUSTLEVEL(wClient(pWin)) != XSecurityClientTrusted) ) return; #ifdef PROPDEBUG /* For testing, it's more convenient if the property rules file gets * reloaded whenever it changes, so we can rapidly try things without * having to reset the server. */ { struct stat buf; static time_t lastmod = 0; int ret = stat(SecurityPolicyFile , &buf); if ( (ret == 0) && (buf.st_mtime > lastmod) ) { ErrorF("reloading property rules\n"); SecurityFreePropertyAccessList(); SecurityLoadPropertyAccessList(); lastmod = buf.st_mtime; } } #endif /* If the property atom is bigger than any atoms on the list, * we know we won't find it, so don't even bother looking. */ if (propertyName <= SecurityMaxPropertyName) { /* untrusted client operating on trusted window; see if it's allowed */ for (pacl = PropertyAccessList; pacl; pacl = pacl->next) { if (pacl->name < propertyName) continue; if (pacl->name > propertyName) break; /* pacl->name == propertyName, so see if it applies to this window */ switch (pacl->windowRestriction) { case SecurityAnyWindow: /* always applies */ break; case SecurityRootWindow: { /* if not a root window, this rule doesn't apply */ if (pWin->parent) continue; } break; case SecurityWindowWithProperty: { PropertyPtr pProp = wUserProps (pWin); Bool match = FALSE; char *p; char *pEndData; while (pProp) { if (pProp->propertyName == pacl->mustHaveProperty) break; pProp = pProp->next; } if (!pProp) continue; if (!pacl->mustHaveValue) break; if (pProp->type != XA_STRING || pProp->format != 8) continue; p = pProp->data; pEndData = ((char *)pProp->data) + pProp->size; while (!match && p < pEndData) { if (SecurityMatchString(pacl->mustHaveValue, p)) match = TRUE; else { /* skip to the next string */ while (*p++ && p < pEndData) ; } } if (!match) continue; } break; /* end case SecurityWindowWithProperty */ } /* end switch on windowRestriction */ /* If we get here, the property access rule pacl applies. * If pacl doesn't apply, something above should have * executed a continue, which will skip the follwing code. */ action = XaceAllowOperation; if (access_mode & DixReadAccess) action = max(action, pacl->readAction); if (access_mode & DixWriteAccess) action = max(action, pacl->writeAction); if (access_mode & DixDestroyAccess) action = max(action, pacl->destroyAction); break; } /* end for each pacl */ } /* end if propertyName <= SecurityMaxPropertyName */ if (XaceAllowOperation != action) { /* audit the access violation */ int cid = CLIENT_ID(pWin->drawable.id); int reqtype = ((xReq *)client->requestBuffer)->reqType; char *actionstr = (XaceIgnoreOperation == action) ? "ignored" : "error"; SecurityAudit("client %d attempted request %d with window 0x%x property %s (atom 0x%x) of client %d, %s\n", client->index, reqtype, pWin->drawable.id, NameForAtom(propertyName), propertyName, cid, actionstr); } /* return codes increase with strictness */ if (action > rec->rval) rec->rval = action; } /* SecurityCheckPropertyAccess */ /* SecurityResetProc * * Arguments: * extEntry is the extension information for the security extension. * * Returns: nothing. * * Side Effects: * Performs any cleanup needed by Security at server shutdown time. */ static void SecurityResetProc( ExtensionEntry *extEntry) { SecurityFreePropertyAccessList(); SecurityFreeSitePolicyStrings(); } /* SecurityResetProc */ int XSecurityOptions(argc, argv, i) int argc; char **argv; int i; { if (strcmp(argv[i], "-sp") == 0) { if (i < argc) SecurityPolicyFile = argv[++i]; return (i + 1); } return (i); } /* XSecurityOptions */ /* SecurityExtensionSetup * * Arguments: none. * * Returns: nothing. * * Side Effects: * Sets up the Security extension if possible. * This function contains things that need to be done * before any other extension init functions get called. */ void SecurityExtensionSetup(INITARGS) { /* Allocate the client private index */ securityClientPrivateIndex = AllocateClientPrivateIndex(); if (!AllocateClientPrivate(securityClientPrivateIndex, sizeof (SecurityClientStateRec))) FatalError("SecurityExtensionSetup: Can't allocate client private.\n"); /* Allocate the extension private index */ securityExtnsnPrivateIndex = AllocateExtensionPrivateIndex(); if (!AllocateExtensionPrivate(securityExtnsnPrivateIndex, 0)) FatalError("SecurityExtensionSetup: Can't allocate extnsn private.\n"); /* register callbacks */ #define XaceRC XaceRegisterCallback XaceRC(XACE_RESOURCE_ACCESS, SecurityCheckResourceIDAccess, NULL); XaceRC(XACE_DEVICE_ACCESS, SecurityCheckDeviceAccess, NULL); XaceRC(XACE_PROPERTY_ACCESS, SecurityCheckPropertyAccess, NULL); XaceRC(XACE_DRAWABLE_ACCESS, SecurityCheckDrawableAccess, NULL); XaceRC(XACE_MAP_ACCESS, SecurityCheckMapAccess, NULL); XaceRC(XACE_BACKGRND_ACCESS, SecurityCheckBackgrndAccess, NULL); XaceRC(XACE_EXT_DISPATCH, SecurityCheckExtAccess, NULL); XaceRC(XACE_EXT_ACCESS, SecurityCheckExtAccess, NULL); XaceRC(XACE_HOSTLIST_ACCESS, SecurityCheckHostlistAccess, NULL); XaceRC(XACE_DECLARE_EXT_SECURE, SecurityDeclareExtSecure, NULL); } /* SecurityExtensionSetup */ /* SecurityExtensionInit * * Arguments: none. * * Returns: nothing. * * Side Effects: * Enables the Security extension if possible. */ void SecurityExtensionInit(INITARGS) { ExtensionEntry *extEntry; SecurityAuthorizationResType = CreateNewResourceType(SecurityDeleteAuthorization); RTEventClient = CreateNewResourceType( SecurityDeleteAuthorizationEventClient); if (!SecurityAuthorizationResType || !RTEventClient) return; RTEventClient |= RC_NEVERRETAIN; if (!AddCallback(&ClientStateCallback, SecurityClientStateCallback, NULL)) return; extEntry = AddExtension(SECURITY_EXTENSION_NAME, XSecurityNumberEvents, XSecurityNumberErrors, ProcSecurityDispatch, SProcSecurityDispatch, SecurityResetProc, StandardMinorOpcode); SecurityErrorBase = extEntry->errorBase; SecurityEventBase = extEntry->eventBase; EventSwapVector[SecurityEventBase + XSecurityAuthorizationRevoked] = (EventSwapPtr)SwapSecurityAuthorizationRevokedEvent; SecurityLoadPropertyAccessList(); } /* SecurityExtensionInit */